From 15c8aa4f3d656794a1c25587b4da5abe9e0e43de Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 10:51:30 +0200 Subject: [PATCH 001/153] [Compose] Test composition of method with no line info after having it Bug: b/284925475 Change-Id: I58db6515ea6cb59c7ea607d6a801b52a4064da66 --- ...omposeMethodWithLineNumberRemovedTest.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java new file mode 100644 index 0000000000..abd20c6852 --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java @@ -0,0 +1,63 @@ +// Copyright (c) 2023, 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.mappingcompose; + +import static org.junit.Assert.assertThrows; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/* + * This is a regression test for b/284925475. + */ +@RunWith(Parameterized.class) +public class ComposeMethodWithLineNumberRemovedTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "package.FieldDefinition$FullFieldDefinition -> package.internal.Th:", + "package.FieldDefinition -> package.internal.Uh:", + "# {'id':'sourceFile','fileName':'FieldDefinition.java'}", + " 1:1:void ():13:13 -> ", + " 1:1:package.FieldDefinition$FullFieldDefinition asFullFieldDefinition():0:0 -> a", + " # {'id':'com.android.tools.r8.residualsignature'," + + "'signature':'()Lpackage/internal/Th;'}"); + private static final String mappingBar = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "package.ClassReference ->" + " package.ClassReference:", + "package.internal.Th -> package.other_internal.F1:", + "package.internal.Uh -> package.other_internal.M1:", + "# {'id':'sourceFile','fileName':'R8_new_hash_name'}", + " 1:1:void () -> ", + " package.internal.Th a() -> b", + " # {'id':'com.android.tools.r8.residualsignature'," + + "'signature':'()Lpackage/retrace_internal/F1;'}"); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar); + // TODO(b/284925475): We should relax the assertion that line numbers can be removed. + assertThrows(AssertionError.class, () -> MappingComposer.compose(mappingForFoo, mappingForBar)); + } +} From 528b70f2701ec5f06b16e9c649d5cf4734cdebd8 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 10:51:39 +0200 Subject: [PATCH 002/153] [Compose] Relax assertion requiring rewriting of minified range Bug: b/284925475 Change-Id: I6a6e909bad4b9fc0c3863261f380ad7a5b408e2a --- .../tools/r8/naming/ComposingBuilder.java | 3 ++- ...ComposeMethodWithLineNumberRemovedTest.java | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index 22cf5e45da..233042398d 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -629,7 +629,8 @@ private void composeMethodNamings( + firstPositionOfOriginalRange); } } - assert minified.hasValue(); + assert minified.hasValue() + || (mappedRange.minifiedRange == null && mappedRange.originalRange == null); } else { MappedRange existingMappedRange = existingClassBuilder.methodsWithoutPosition.get(signature); diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java index abd20c6852..8e9ea9e01a 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java @@ -4,7 +4,8 @@ package com.android.tools.r8.mappingcompose; -import static org.junit.Assert.assertThrows; +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -52,12 +53,23 @@ public static TestParametersCollection data() { " package.internal.Th a() -> b", " # {'id':'com.android.tools.r8.residualsignature'," + "'signature':'()Lpackage/retrace_internal/F1;'}"); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "package.ClassReference -> package.ClassReference:", + "package.FieldDefinition -> package.other_internal.M1:", + "# {'id':'sourceFile','fileName':'FieldDefinition.java'}", + " package.internal.Th a() -> b", + " # {'id':'com.android.tools.r8.residualsignature'," + + "'signature':'()Lpackage/retrace_internal/F1;'}", + " 1:1:void () -> ", + "package.FieldDefinition$FullFieldDefinition -> package.other_internal.F1:"); @Test public void testCompose() throws Exception { ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo); ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar); - // TODO(b/284925475): We should relax the assertion that line numbers can be removed. - assertThrows(AssertionError.class, () -> MappingComposer.compose(mappingForFoo, mappingForBar)); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); } } From 4f6ce5666ea05fb7b55939906362d8e9a20f727e Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 10:51:47 +0200 Subject: [PATCH 003/153] [Compose] Add test for missing rewrite of outline Bug: b/284925475 Change-Id: Ic2dc9b802307c384209b9db9217499f1432a1cd9 --- .../ComposeOutlineWithIdRangeTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java new file mode 100644 index 0000000000..313c88cedd --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java @@ -0,0 +1,79 @@ +// Copyright (c) 2023, 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.mappingcompose; + +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +public class ComposeOutlineWithIdRangeTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "package.Class$$ExternalSyntheticOutline0 -> package.internal.X:", + "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}", + "# {'id':'com.android.tools.r8.synthesized'}", + " 1:3:long package.Int2IntLinkedOpenHashMap$$InternalSyntheticOutline$HASH$0" + + ".m(long,long,long):0:2" + + " -> a", + " # {'id':'com.android.tools.r8.synthesized'}", + " # {'id':'com.android.tools.r8.outline'}", + "package.Class -> package.internal.Y:", + "# {'id':'sourceFile','fileName':'FieldDefinition.java'}", + " 1:6:void foo():21:26 -> a", + " 7:7:void foo():0:0 -> a", + " # {'id':'com.android.tools.r8.outlineCallsite'," + + "'positions':{'1':10,'2':11}," + + "'outline':'Lpackage/internal/X;a(JJJ)J'}", + " 8:9:void foo():38:39 -> a", + " 10:10:void foo():42:42 -> a", + " 11:11:void foo():44:44 -> a"); + private static final String mappingBar = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "package.internal.X -> package.new_internal.X:", + " 1:3:long a(long,long,long) -> b", + "package.internal.Y -> package.new_internal.Y:", + " 1:9:void a() -> b"); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "package.Class -> package.new_internal.Y:", + "# {'id':'sourceFile','fileName':'FieldDefinition.java'}", + // TODO(b/284925475): We should preserve outline information. + " 1:9:void foo() -> b", + "package.Class$$ExternalSyntheticOutline0 -> package.new_internal.X:", + "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}", + "# {'id':'com.android.tools.r8.synthesized'}", + " 1:3:long package.Int2IntLinkedOpenHashMap$$InternalSyntheticOutline$HASH$0" + + ".m(long,long,long) -> b", + " # {'id':'com.android.tools.r8.synthesized'}", + " # {'id':'com.android.tools.r8.outline'}"); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); + } +} From 243a784529896d077e302b029579124f4f96a2c4 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 10:51:56 +0200 Subject: [PATCH 004/153] [Compose] Fix composition of outline mapped positions Bug: b/284925475 Change-Id: I84133e88fab62c3703f32fc2fd9b7f3d241db691 --- .../r8/naming/ClassNamingForNameMapper.java | 8 ++ .../tools/r8/naming/ComposingBuilder.java | 87 ++++++++++++++----- ...poseDifferentMethodWithLineNumberTest.java | 2 +- ...omposeMethodWithLineNumberRemovedTest.java | 2 +- .../r8/mappingcompose/ComposeOutlineTest.java | 17 ++-- .../ComposeOutlineWithIdRangeTest.java | 18 ++-- 6 files changed, 97 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java index 5a71db96a9..76a188c781 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java @@ -659,6 +659,10 @@ public int getFirstPositionOfOriginalRange(int defaultValue) { } } + public Range getOriginalRangeOrIdentity() { + return originalRange != null ? originalRange : minifiedRange; + } + @Override public Signature getOriginalSignature() { return signature; @@ -776,5 +780,9 @@ public MappedRange partitionOnMinifiedRange(Range minifiedRange) { } return splitMappedRange; } + + public boolean isOriginalRangePreamble() { + return originalRange != null && originalRange.isPreamble(); + } } } diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index 233042398d..9073ff3ef1 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -408,7 +408,7 @@ private MappedRangeOriginalToMinifiedMap(Int2ReferenceMap> origina private static MappedRangeOriginalToMinifiedMap build(List mappedRanges) { Int2ReferenceMap> positionMap = new Int2ReferenceOpenHashMap<>(); for (MappedRange mappedRange : mappedRanges) { - Range originalRange = mappedRange.originalRange; + Range originalRange = mappedRange.getOriginalRangeOrIdentity(); for (int position = originalRange.from; position <= originalRange.to; position++) { // It is perfectly fine to have multiple minified ranges mapping to the same source, we // just need to keep the additional information. @@ -608,7 +608,7 @@ private void composeMethodNamings( listSegmentTree.findEntry(firstPositionOfOriginalRange); if (existingEntry == null && firstPositionOfOriginalRange == 0 - && !mappedRange.originalRange.isPreamble()) { + && !mappedRange.isOriginalRangePreamble()) { existingEntry = listSegmentTree.findEntry(mappedRange.getLastPositionOfOriginalRange()); } @@ -619,8 +619,7 @@ private void composeMethodNamings( } else { // The original can be discarded if it no longer exists or if the method is // non-throwing. - if (mappedRange.originalRange != null - && !mappedRange.originalRange.isPreamble() + if (!mappedRange.isOriginalRangePreamble() && !options.mappingComposeOptions().allowNonExistingOriginalRanges) { throw new MappingComposeException( "Could not find original starting position of '" @@ -629,8 +628,7 @@ private void composeMethodNamings( + firstPositionOfOriginalRange); } } - assert minified.hasValue() - || (mappedRange.minifiedRange == null && mappedRange.originalRange == null); + assert minified.hasValue() || mappedRange.getOriginalRangeOrIdentity() == null; } else { MappedRange existingMappedRange = existingClassBuilder.methodsWithoutPosition.get(signature); @@ -682,22 +680,66 @@ private void composeMethodNamings( ConsumerUtils.emptyConsumer()); } if (!computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) { - MappedRangeOriginalToMinifiedMap originalToMinifiedMap = - MappedRangeOriginalToMinifiedMap.build(newMappedRanges); + // Outline positions are synthetic positions and they have no position in the residual + // program. We therefore have to find the original positions and copy all inline frames + // and amend the outermost frame with the residual signature and the next free position. List outlineCallSites = new ArrayList<>( computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp); outlineCallSites.sort(Comparator.comparing(mapping -> mapping.getOutline().toString())); + int firstAvailableRange = lastComposedRange.minifiedRange.to + 1; for (OutlineCallsiteMappingInformation outlineCallSite : outlineCallSites) { Int2IntSortedMap positionMap = outlineCallSite.getPositions(); + MethodSignature originalSignature = + memberNaming.getOriginalSignature().asMethodSignature(); + ComposingClassBuilder existingClassBuilderForOutline = + getExistingClassBuilder(originalSignature); + if (existingClassBuilderForOutline == null) { + assert false; + continue; + } + SegmentTree> outlineSegmentTree = + existingClassBuilderForOutline.methodsWithPosition.get(originalSignature); + if (outlineSegmentTree == null) { + assert false; + continue; + } + Int2IntSortedMap newPositionMap = new Int2IntLinkedOpenHashMap(positionMap.size()); for (Integer keyPosition : positionMap.keySet()) { int keyPositionInt = keyPosition; int originalDestination = positionMap.get(keyPositionInt); - int newDestination = originalToMinifiedMap.lookupFirst(originalDestination); - positionMap.put(keyPositionInt, newDestination); + ExistingMapping existingMapping = + computeExistingMapping(outlineSegmentTree.find(originalDestination)); + List mappedRangesForOutlinePosition = + existingMapping.getMappedRangesForPosition(originalDestination); + if (mappedRangesForOutlinePosition == null) { + assert false; + continue; + } + MappedRange outerMostOutlineFrame = ListUtils.last(mappedRangesForOutlinePosition); + assert outerMostOutlineFrame.minifiedRange.span() == 1; + Range newMinifiedRange = new Range(firstAvailableRange, firstAvailableRange); + for (MappedRange inlineMappedRangeInOutlinePosition : + mappedRangesForOutlinePosition) { + if (inlineMappedRangeInOutlinePosition != outerMostOutlineFrame) { + composedRanges.add( + new MappedRange( + newMinifiedRange, + inlineMappedRangeInOutlinePosition.signature, + inlineMappedRangeInOutlinePosition.getOriginalRangeOrIdentity(), + inlineMappedRangeInOutlinePosition.getRenamedName())); + } + } + composedRanges.add( + new MappedRange( + newMinifiedRange, + lastComposedRange.signature, + outerMostOutlineFrame.originalRange, + lastComposedRange.getRenamedName())); + newPositionMap.put(keyPositionInt, firstAvailableRange); + firstAvailableRange = newMinifiedRange.to + 1; } - lastComposedRange.addMappingInformation( - outlineCallSite, ConsumerUtils.emptyConsumer()); + outlineCallSite.setPositionsInternal(newPositionMap); } } MethodSignature residualSignature = @@ -794,7 +836,7 @@ private List composeMappedRangesForMethod( return Collections.singletonList(newRange); } MappedRange lastExistingRange = ListUtils.last(existingRanges); - if (newRange.originalRange == null) { + if (newRange.getOriginalRangeOrIdentity() == null) { MappedRange newComposedRange = new MappedRange( newRange.minifiedRange, lastExistingRange.signature, null, newRange.renamedName); @@ -816,7 +858,7 @@ private List composeMappedRangesForMethod( if (existingMappedRanges == null) { // If we cannot lookup the original position because it has been removed we compose with // the existing method signature. - if (newRange.originalRange.isPreamble() + if (newRange.isOriginalRangePreamble() || (existingRanges.size() == 1 && lastExistingRange.minifiedRange == null)) { return Collections.singletonList( new MappedRange( @@ -827,7 +869,7 @@ private List composeMappedRangesForMethod( ? lastExistingRange.originalRange : EMPTY_RANGE, newRange.renamedName)); - } else if (newRange.originalRange.from == 0) { + } else if (newRange.getOriginalRangeOrIdentity().from == 0) { // Similar to the trick below we create a synthetic range to map the preamble to. Pair emptyRange = createEmptyRange( @@ -843,7 +885,7 @@ private List composeMappedRangesForMethod( assert lastExistingMappedRange != null; // If the existing mapped minified range is equal to the original range of the new range // then we have a perfect mapping that we can translate directly. - if (lastExistingMappedRange.minifiedRange.equals(newRange.originalRange)) { + if (lastExistingMappedRange.minifiedRange.equals(newRange.getOriginalRangeOrIdentity())) { computeComposedMappedRange( newComposedRanges, newRange, @@ -1011,9 +1053,9 @@ private void computeComposedMappedRange( Range existingRange = existingMappedRanges.get(0).minifiedRange; assert existingMappedRanges.stream().allMatch(x -> x.minifiedRange.equals(existingRange)); Range newMinifiedRange = new Range(lastStartingMinifiedFrom, position); - boolean copyOriginalRange = existingRange.equals(newMappedRange.originalRange); + boolean copyOriginalRange = existingRange.equals(newMappedRange.getOriginalRangeOrIdentity()); for (MappedRange existingMappedRange : existingMappedRanges) { - Range existingOriginalRange = existingMappedRange.originalRange; + Range existingOriginalRange = existingMappedRange.getOriginalRangeOrIdentity(); Range newOriginalRange; if (copyOriginalRange || existingOriginalRange == null @@ -1023,7 +1065,7 @@ private void computeComposedMappedRange( // Find the window that the new range points to into the original range. int existingMinifiedPos = newMappedRange.getOriginalLineNumber(lastStartingMinifiedFrom); int newOriginalStart = existingMappedRange.getOriginalLineNumber(existingMinifiedPos); - if (newMappedRange.originalRange.span() == 1) { + if (newMappedRange.getOriginalRangeOrIdentity().span() == 1) { newOriginalRange = new Range(newOriginalStart, newOriginalStart); } else { assert newMinifiedRange.span() <= existingOriginalRange.span(); @@ -1045,12 +1087,13 @@ private void computeComposedMappedRange( if (info.isOutlineMappingInformation()) { computedOutlineInformation.seenOutlineMappingInformation = info.asOutlineMappingInformation(); - } else if (info.isOutlineCallsiteInformation()) { + return; + } + if (info.isOutlineCallsiteInformation()) { computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.add( info.asOutlineCallsiteInformation()); - } else { - mappingInformationToCompose.add(info); } + mappingInformationToCompose.add(info); }); composeMappingInformation( computedRange.getAdditionalMappingInformation(), diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java index a6aded1f0b..132b0f59a8 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java @@ -46,7 +46,7 @@ public static TestParametersCollection data() { "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "com.foo -> b:", " 1:1:int f1(boolean) -> f2", - " 8:8:void f1(int) -> f3", + " 8:8:void f1(int):2:2 -> f3", " # {'id':'com.android.tools.r8.residualsignature','signature':'(Z)V'}"); @Test diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java index 8e9ea9e01a..9b28d99ba8 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java @@ -62,7 +62,7 @@ public static TestParametersCollection data() { " package.internal.Th a() -> b", " # {'id':'com.android.tools.r8.residualsignature'," + "'signature':'()Lpackage/retrace_internal/F1;'}", - " 1:1:void () -> ", + " 1:1:void ():13:13 -> ", "package.FieldDefinition$FullFieldDefinition -> package.other_internal.F1:"); @Test diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java index 657b551c02..f722cc3fa8 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java @@ -41,8 +41,9 @@ public static TestParametersCollection data() { " 5:5:int foo.bar.baz.outlineCaller(int):98:98 -> s", " 5:5:int outlineCaller(int):24 -> s", " 27:27:int outlineCaller(int):0:0 -> s", - " # { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '1': 4, '2': 5 }," - + " 'outline':'La;a()I' }"); + " # { 'id':'com.android.tools.r8.outlineCallsite'," + + "'positions': { '1': 4, '2': 5 }," + + "'outline':'La;a()I' }"); private static final String mappingBar = StringUtils.unixLines("# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "a -> b:"); private static final String mappingBaz = @@ -51,18 +52,18 @@ public static TestParametersCollection data() { "b -> c:", " 4:5:int a():1:2 -> m", "x -> y:", - " 8:9:int s(int):4:5 -> o", " 42:42:int s(int):27:27 -> o"); private static final String mappingResult = StringUtils.unixLines( "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "outline.Callsite -> y:", - " 8:8:int outlineCaller(int):23 -> o", - " 9:9:int foo.bar.baz.outlineCaller(int):98:98 -> o", - " 9:9:int outlineCaller(int):24 -> o", " 42:42:int outlineCaller(int):0:0 -> o", - " #" - + " {'id':'com.android.tools.r8.outlineCallsite','positions':{'4':8,'5':9},'outline':'Lc;m()I'}", + " # {'id':'com.android.tools.r8.outlineCallsite'," + + "'positions':{'4':43,'5':44}," + + "'outline':'Lc;m()I'}", + " 43:43:int outlineCaller(int):23 -> o", + " 44:44:int foo.bar.baz.outlineCaller(int):98:98 -> s", + " 44:44:int outlineCaller(int):24 -> o", "outline.Class -> c:", " 4:5:int some.inlinee():75:76 -> m", " 4:5:int outline():0 -> m", diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java index 313c88cedd..20b1cb89b4 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java @@ -45,7 +45,8 @@ public static TestParametersCollection data() { + "'positions':{'1':10,'2':11}," + "'outline':'Lpackage/internal/X;a(JJJ)J'}", " 8:9:void foo():38:39 -> a", - " 10:10:void foo():42:42 -> a", + " 10:10:void inlineeInOutline():1337:1337 -> a", + " 10:10:void foo():42 -> a", " 11:11:void foo():44:44 -> a"); private static final String mappingBar = StringUtils.unixLines( @@ -53,19 +54,26 @@ public static TestParametersCollection data() { "package.internal.X -> package.new_internal.X:", " 1:3:long a(long,long,long) -> b", "package.internal.Y -> package.new_internal.Y:", - " 1:9:void a() -> b"); + " 1:8:void a() -> b"); private static final String mappingResult = StringUtils.unixLines( "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "package.Class -> package.new_internal.Y:", "# {'id':'sourceFile','fileName':'FieldDefinition.java'}", - // TODO(b/284925475): We should preserve outline information. - " 1:9:void foo() -> b", + " 1:6:void foo():21:26 -> b", + " 7:7:void foo():0:0 -> b", + " # {'id':'com.android.tools.r8.outlineCallsite'," + + "'positions':{'1':9,'2':10}," + + "'outline':'Lpackage/new_internal/X;b(JJJ)J'}", + " 8:8:void foo():38:38 -> b", + " 9:9:void inlineeInOutline():1337:1337 -> a", + " 9:9:void foo():42 -> b", + " 10:10:void foo():44:44 -> b", "package.Class$$ExternalSyntheticOutline0 -> package.new_internal.X:", "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}", "# {'id':'com.android.tools.r8.synthesized'}", " 1:3:long package.Int2IntLinkedOpenHashMap$$InternalSyntheticOutline$HASH$0" - + ".m(long,long,long) -> b", + + ".m(long,long,long):0:2 -> b", " # {'id':'com.android.tools.r8.synthesized'}", " # {'id':'com.android.tools.r8.outline'}"); From 5733fb92f1139e24102f6e5ffb61e98f90aa7f4f Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 10:52:03 +0200 Subject: [PATCH 005/153] [Compose] Add test for inlining an outline Bug: b/284925475 Change-Id: I43302357d1772cc62821eda5126faa6f1b2a8bbc --- .../ComposeOutlineInlineTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java new file mode 100644 index 0000000000..9203fed0d1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java @@ -0,0 +1,92 @@ +// Copyright (c) 2023, 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.mappingcompose; + +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +public class ComposeOutlineInlineTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "package.Class$$ExternalSyntheticOutline0 -> package.internal.X:", + "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}", + "# {'id':'com.android.tools.r8.synthesized'}", + " 1:2:long package.Int2IntLinkedOpenHashMap$$InternalSyntheticOutline$HASH$0" + + ".m(long,long,long):0:1" + + " -> a", + " # {'id':'com.android.tools.r8.synthesized'}", + " # {'id':'com.android.tools.r8.outline'}", + "package.Class -> package.internal.Y:", + "# {'id':'sourceFile','fileName':'FieldDefinition.java'}", + " 1:6:void foo():21:26 -> a", + " 7:7:void foo():0:0 -> a", + " # {'id':'com.android.tools.r8.outlineCallsite'," + + "'positions':{'1':10,'2':11}," + + "'outline':'Lpackage/internal/X;a(JJJ)J'}", + " 8:9:void foo():38:39 -> a", + " 10:10:void inlineeInOutline():1337:1337 -> a", + " 10:10:void foo():42 -> a", + " 11:11:void foo():44:44 -> a"); + private static final String mappingBar = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "package.internal.Y -> package.new_internal.Y:", + " 1:6:void a() -> b", + " 7:8:long package.internal.X.a(long,long,long):1:2 -> b", + " 7:8:void a():7 -> b", + " 9:10:void a():8:9 -> b"); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "package.Class -> package.new_internal.Y:", + "# {'id':'sourceFile','fileName':'FieldDefinition.java'}", + " 1:6:void foo():21:26 -> b", + // TODO(b/284925475): We should put in the inline positions here instead of a reference to + // the outline + " 7:8:long package.Int2IntLinkedOpenHashMap$$InternalSyntheticOutline$HASH$0" + + ".m(long,long,long):0:1 -> b", + // TODO(b/284925475): This is not synthesized. + " # {'id':'com.android.tools.r8.synthesized'}", + " 7:8:void foo():0:0 -> b", + " # {'id':'com.android.tools.r8.outlineCallsite'," + + "'positions':{'1':11,'2':12}," + + "'outline':'Lpackage/internal/X;a(JJJ)J'}", + " 9:10:void foo():38:39 -> b", + // TODO(b/284925475): This is not an outline or outline call site. + " # {'id':'com.android.tools.r8.outline'}", + " 11:11:void inlineeInOutline():1337:1337 -> a", + " 11:11:void foo():42 -> b", + " 12:12:void foo():44:44 -> b", + "package.Class$$ExternalSyntheticOutline0 -> package.internal.X:", + "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}", + "# {'id':'com.android.tools.r8.synthesized'}"); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); + } +} From 29cd893442a4c8f558ca504fa5e329751e7f5715 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Mon, 22 May 2023 14:27:25 +0200 Subject: [PATCH 006/153] Add test for dynamically changing line information Bug: b/283757617 Change-Id: I1498f17cc32a36c707798c07cd4c314c4f49f971 --- ...ntalMergingWithTryCatchLineNumberTest.java | 99 +++++++++++++++++++ .../retrace/classes/SynthesizeLineNumber.java | 71 +++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/retrace/HorizontalMergingWithTryCatchLineNumberTest.java create mode 100644 src/test/java/com/android/tools/r8/retrace/classes/SynthesizeLineNumber.java diff --git a/src/test/java/com/android/tools/r8/retrace/HorizontalMergingWithTryCatchLineNumberTest.java b/src/test/java/com/android/tools/r8/retrace/HorizontalMergingWithTryCatchLineNumberTest.java new file mode 100644 index 0000000000..ff42a2de6b --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/HorizontalMergingWithTryCatchLineNumberTest.java @@ -0,0 +1,99 @@ +// Copyright (c) 2023, 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.retrace; + +import static com.android.tools.r8.naming.retrace.StackTrace.isSame; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.retrace.StackTrace; +import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; +import com.android.tools.r8.retrace.classes.SynthesizeLineNumber; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * This is a test for b/283757617 showing how we potentially could change line information at + * runtime. + */ +@RunWith(Parameterized.class) +public class HorizontalMergingWithTryCatchLineNumberTest extends TestBase { + + private static final String FILENAME = "SynthesizeLineNumber.java"; + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + private static final StackTrace expectedStackTrace = + StackTrace.builder() + .add( + StackTraceLine.builder() + .setClassName(typeName(SynthesizeLineNumber.A.class)) + .setMethodName("foo") + .setFileName(FILENAME) + .setLineNumber(14) + .build()) + .add( + StackTraceLine.builder() + .setClassName(typeName(SynthesizeLineNumber.Main.class)) + .setMethodName("call") + .setFileName(FILENAME) + .setLineNumber(34) + .build()) + .add( + StackTraceLine.builder() + .setClassName(typeName(SynthesizeLineNumber.Main.class)) + .setMethodName("main") + .setFileName(FILENAME) + .setLineNumber(28) + .build()) + .build(); + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addInnerClasses(SynthesizeLineNumber.class) + .run(parameters.getRuntime(), SynthesizeLineNumber.Main.class, "normal") + .inspectStackTrace(stackTrace -> assertThat(stackTrace, isSame(expectedStackTrace))); + } + + @Test + public void testR8() throws Exception { + R8TestCompileResult compilationResult = + testForR8(parameters.getBackend()) + .addInnerClasses(SynthesizeLineNumber.class) + .setMinApi(parameters) + .addKeepMainRule(SynthesizeLineNumber.Main.class) + .addKeepAttributeLineNumberTable() + .enableInliningAnnotations() + .addHorizontallyMergedClassesInspector( + inspector -> + inspector.assertClassesMerged( + SynthesizeLineNumber.A.class, SynthesizeLineNumber.B.class)) + .compile(); + // Mock changes to proguard map to simulate what R8 could emit. + String proguardMap = + compilationResult.getProguardMap() + + "\n 1000:1000:void call(int,boolean):34:34 -> main" + + "\n 1000:1000:void main(java.lang.String[]):28 -> main"; + compilationResult + .run(parameters.getRuntime(), SynthesizeLineNumber.Main.class, "synthesize") + .inspectOriginalStackTrace( + originalStackTrace -> { + StackTrace retraced = originalStackTrace.retrace(proguardMap); + assertThat(retraced, isSame(expectedStackTrace)); + }); + } +} diff --git a/src/test/java/com/android/tools/r8/retrace/classes/SynthesizeLineNumber.java b/src/test/java/com/android/tools/r8/retrace/classes/SynthesizeLineNumber.java new file mode 100644 index 0000000000..a2b4c80fc1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/classes/SynthesizeLineNumber.java @@ -0,0 +1,71 @@ +// Copyright (c) 2023, 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.retrace.classes; + +import com.android.tools.r8.NeverInline; + +public class SynthesizeLineNumber { + + public static class A { + @NeverInline + public static void foo() throws Exception { + throw new Exception("A::foo"); + } + } + + public static class B { + @NeverInline + public static void bar() throws Exception { + throw new Exception("B::bar"); + } + } + + public static class Main { + + public static void main(String[] args) throws Exception { + call(System.currentTimeMillis() > 0 ? 0 : 1, args[0].equals("synthesize")); + } + + public static void call(int classId, boolean shouldSynthesizeLineNumber) throws Exception { + try { + if (classId == 0) { + A.foo(); + } else { + B.bar(); + } + } catch (Exception e) { + if (shouldSynthesizeLineNumber) { + Exception exception = new Exception(e.getMessage(), e.getCause()); + StackTraceElement[] stackTrace = e.getStackTrace(); + // The obfuscated class name after repackaging and class name minification. + String className = "com.android.tools.r8.retrace.classes.SynthesizeLineNumber$Main"; + // The obfuscated method name after minification. Since this is inlined into main it is + // just main. + String obfuscatedMethodName = "main"; + for (int i = 0; i < stackTrace.length; i++) { + StackTraceElement original = stackTrace[i]; + // The line number range will need to be the range of the try block for catching the + // exception. + if (original.getClassName().equals(className) + && original.getMethodName().equals(obfuscatedMethodName) + && original.getLineNumber() >= 0 + && original.getLineNumber() <= 10) { + stackTrace[i] = + new StackTraceElement( + original.getClassName(), + original.getMethodName(), + original.getFileName(), + classId + 1000); + break; + } + } + exception.setStackTrace(stackTrace); + throw exception; + } + throw e; + } + } + } +} From b01011fc5ff5de32d49e1e71e628d9399bd1112c Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 11:06:29 +0200 Subject: [PATCH 007/153] Add test for missing rewriting of moved method Bug: b/269178203 Change-Id: I8b6365e8e6759d6f421f1e303f8f0708d842fbcd --- .../ApplyMappingMovedMethodTest.java | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMovedMethodTest.java diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMovedMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMovedMethodTest.java new file mode 100644 index 0000000000..00592977d4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMovedMethodTest.java @@ -0,0 +1,137 @@ +// Copyright (c) 2023, 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.naming.applymapping; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MemberSubject; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** This is a reproduction of b/269178203. */ +@RunWith(Parameterized.class) +public class ApplyMappingMovedMethodTest extends TestBase { + + private static final String mapping = + StringUtils.lines( + "com.android.tools.r8.naming.applymapping.ApplyMappingMovedMethodTest$One -> b.b:", + " void foo() -> m1", + " void" + + " com.android.tools.r8.naming.applymapping.ApplyMappingMovedMethodTest$Other.foo()" + + " -> m2"); + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testR8ApplyMappingSameCompilation() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(One.class, Other.class, Main.class) + .setMinApi(parameters) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .addApplyMapping(mapping) + .addHorizontallyMergedClassesInspector( + inspector -> inspector.assertClassesMerged(One.class, Other.class)) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("One::foo") + .inspect( + inspector -> { + ClassSubject clazz = inspector.clazz("b.b"); + assertThat(clazz, isPresent()); + Set expected = new HashSet<>(); + expected.add("m1"); + // TODO(b/269178203): We should be able to rewrite this to m2. + expected.add("a"); + assertEquals( + expected, + clazz.allMethods().stream() + .map(MemberSubject::getFinalName) + .collect(Collectors.toSet())); + }); + } + + @Test + public void testR8TestReference() throws Exception { + R8TestCompileResult libraryResult = + testForR8(parameters.getBackend()) + .addProgramClasses(One.class, Other.class, Main.class) + .setMinApi(parameters) + .addKeepMainRule(Main.class) + .addHorizontallyMergedClassesInspector( + inspector -> inspector.assertClassesMerged(One.class, Other.class)) + .enableInliningAnnotations() + .compile(); + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addClasspathClasses(One.class, Other.class) + .addKeepAllClassesRule() + .addApplyMapping(libraryResult.getProguardMap()) + .setMinApi(parameters) + .compile() + .addRunClasspathFiles(libraryResult.writeToZip()) + .run(parameters.getRuntime(), TestClass.class) + // TODO(b/269178203): We could rewrite calls to m2 but it would be better to just keep the + // endpoints in the program when tests are referencing it - in this example it is this + // compilation that is the test and it referencing the libraryResult, which is the program. + .assertFailureWithErrorThatThrows(NoClassDefFoundError.class) + .assertFailureWithErrorThatMatches(containsString(typeName(Other.class))); + } + + public static class One { + + @NeverInline + public static void foo() { + System.out.println("One::foo"); + } + } + + public static class Other { + + @NeverInline + public static void foo() { + System.out.println("Other::foo"); + } + } + + public static class Main { + + public static void main(String[] args) { + if (System.currentTimeMillis() > 0) { + One.foo(); + } else { + Other.foo(); + } + } + } + + public static class TestClass { + + public static void main(String[] args) { + One.foo(); + Other.foo(); + } + } +} From 7edcfbc5676991e0631b462a7c813fd1c9d4e4e4 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 11:12:38 +0200 Subject: [PATCH 008/153] Remove unused method in ComposingBuilder Change-Id: I494c02c0816d56b47b11f9e6bfe02df240a3cb31 --- .../java/com/android/tools/r8/naming/ComposingBuilder.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index 9073ff3ef1..b272acadac 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -420,11 +420,6 @@ private static MappedRangeOriginalToMinifiedMap build(List mappedRa return new MappedRangeOriginalToMinifiedMap(positionMap); } - public int lookupFirst(int originalPosition) { - List minifiedPositions = originalToMinified.get(originalPosition); - return minifiedPositions == null ? 0 : minifiedPositions.get(0); - } - public void visitMinified(int originalPosition, Consumer consumer) { List minifiedPositions = originalToMinified.get(originalPosition); if (minifiedPositions != null) { From 941a62cce59a1104a98eeb0e1b7a0bc5fe74a4ac Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Tue, 6 Jun 2023 12:42:45 +0200 Subject: [PATCH 009/153] Avoid using SSA value number as value identity This also extends the consistent SSA to check that the numbers are unique as the determinism of the sorting still relies on uniqueness. Change-Id: Ia09a11fff88d353168cc94c67f7c715b938968e3 --- .../com/android/tools/r8/ir/code/IRCode.java | 25 +++++++++++++------ .../r8/ir/conversion/callgraph/CallGraph.java | 7 +++++- .../InlineCandidateProcessor.java | 5 ++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index a46b0a2e80..edf02466e4 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java @@ -42,6 +42,8 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.collect.Streams; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -692,18 +694,25 @@ public boolean hasCatchHandlers() { return false; } - private boolean consistentDefUseChains() { - Set values = Sets.newIdentityHashSet(); + private static void addValueAndCheckUniqueNumber(Int2ReferenceMap values, Value value) { + assert value != null; + int number = value.getNumber(); + Value old = values.put(number, value); + assert old == null || old == value || (number == -1 && value.isValueOnStack()) + : "Multiple value definitions with number " + number + ": " + value + " and " + old; + } + private boolean consistentDefUseChains() { + Int2ReferenceMap values = new Int2ReferenceOpenHashMap<>(); for (BasicBlock block : blocks) { int predecessorCount = block.getPredecessors().size(); // Check that all phi uses are consistent. for (Phi phi : block.getPhis()) { assert !phi.isTrivialPhi(); assert phi.getOperands().size() == predecessorCount; - values.add(phi); + addValueAndCheckUniqueNumber(values, phi); for (Value value : phi.getOperands()) { - values.add(value); + addValueAndCheckUniqueNumber(values, value); assert value.uniquePhiUsers().contains(phi); assert !phi.hasLocalInfo() || phi.getLocalInfo() == value.getLocalInfo(); assert value.isPhi() || value.definition.hasBlock(); @@ -713,21 +722,21 @@ private boolean consistentDefUseChains() { assert instruction.getBlock() == block; Value outValue = instruction.outValue(); if (outValue != null) { - values.add(outValue); + addValueAndCheckUniqueNumber(values, outValue); assert outValue.definition == instruction; } for (Value value : instruction.inValues()) { - values.add(value); + addValueAndCheckUniqueNumber(values, value); assert value.uniqueUsers().contains(instruction); } for (Value value : instruction.getDebugValues()) { - values.add(value); + addValueAndCheckUniqueNumber(values, value); assert value.debugUsers().contains(instruction); } } } - for (Value value : values) { + for (Value value : values.values()) { assert verifyValue(value); assert consistentValueUses(value); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java index 5560d5b65c..313f4d1fe9 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java @@ -9,7 +9,9 @@ import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation.CallGraphBasedCallSiteInformation; import com.android.tools.r8.ir.conversion.callgraph.CycleEliminator.CycleEliminationResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.collections.ProgramMethodSet; +import com.android.tools.r8.utils.collections.SortedProgramMethodSet; import com.google.common.collect.Sets; import java.util.Collection; import java.util.Iterator; @@ -77,7 +79,10 @@ public ProgramMethodSet extractRoots() { } private ProgramMethodSet extractNodes(Predicate predicate, Consumer clean) { - ProgramMethodSet result = ProgramMethodSet.create(); + ProgramMethodSet result = + InternalOptions.DETERMINISTIC_DEBUGGING + ? SortedProgramMethodSet.create() + : ProgramMethodSet.create(); Set removed = Sets.newIdentityHashSet(); Iterator nodeIterator = nodes.values().iterator(); while (nodeIterator.hasNext()) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java index 69d5822ddf..088c331210 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java @@ -80,7 +80,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeSet; import java.util.function.Function; final class InlineCandidateProcessor { @@ -695,8 +694,7 @@ private void removeFieldReads(IRCode code, AssumeRemover assumeRemover) { private void removeFieldReadsFromNewInstance( IRCode code, Set affectedValues, AssumeRemover assumeRemover) { - TreeSet uniqueInstanceGetUsersWithDeterministicOrder = - new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber())); + List uniqueInstanceGetUsersWithDeterministicOrder = new ArrayList<>(); for (Instruction user : eligibleInstance.uniqueUsers()) { if (user.isInstanceGet()) { assumeRemover.markAssumeDynamicTypeUsersForRemoval(user.outValue()); @@ -722,6 +720,7 @@ private void removeFieldReadsFromNewInstance( + user); } + uniqueInstanceGetUsersWithDeterministicOrder.sort(Comparator.comparing(Instruction::outValue)); Map fieldHelpers = new IdentityHashMap<>(); for (InstanceGet user : uniqueInstanceGetUsersWithDeterministicOrder) { // Replace a field read with appropriate value. From e95125e5d3d5f4f784a2966f30348678c4191726 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 11:26:23 +0200 Subject: [PATCH 010/153] Reproduce removal of single caller after inlining with startup profile Bug: b/285021603 Change-Id: I51acbd8612106d4c75a4d53ef04e259fdcefb161 --- .../SingleCallerBridgeStartupTest.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java diff --git a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java new file mode 100644 index 0000000000..4a27e0fef6 --- /dev/null +++ b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java @@ -0,0 +1,89 @@ +// Copyright (c) 2023, 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.startup; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.startup.profile.ExternalStartupItem; +import com.android.tools.r8.startup.profile.ExternalStartupMethod; +import com.android.tools.r8.startup.utils.StartupTestingUtils; +import com.google.common.collect.ImmutableList; +import java.util.Collection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** Regression test for b/285021603. */ +@RunWith(Parameterized.class) +public class SingleCallerBridgeStartupTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + // Create a startup profile with method A.bar() and A.callBarInStartup() to allow inlining + // of A.bar() into A.callBarInStartup(). + MethodReference barMethod = Reference.methodFromMethod(A.class.getDeclaredMethod("bar")); + Collection startupProfile = + ImmutableList.of( + ExternalStartupMethod.builder().setMethodReference(barMethod).build(), + ExternalStartupMethod.builder() + .setMethodReference( + Reference.methodFromMethod(A.class.getDeclaredMethod("callBarInStartup"))) + .build()); + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .apply(testBuilder -> StartupTestingUtils.addStartupProfile(testBuilder, startupProfile)) + .addOptionsModification( + options -> + // Simulate proto optimization where we allow reprocessing of inlinee. + options.inlinerOptions().applyInliningToInlineePredicateForTesting = + (appView, inlinee, inliningDepth) -> + inlinee.getMethodReference().equals(barMethod)) + .setMinApi(parameters) + .run(parameters.getRuntime(), Main.class) + // TODO(b/285021603): We should not fail here. + .assertFailureWithErrorThatThrows(NoSuchMethodError.class); + } + + static class Main { + + public static void main(String[] args) { + A.callBarInStartup(); + A.callBarOutsideStartup(); + } + } + + public static class A { + + private static void foo() { + System.out.println("A::foo"); + } + + private static void bar() { + foo(); + } + + public static void callBarInStartup() { + bar(); + } + + public static void callBarOutsideStartup() { + bar(); + } + } +} From 5f69e7b6dccf24ebd760f75784d1784c2e36ecfa Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 11:38:50 +0200 Subject: [PATCH 011/153] Keep singler caller inlinees after inlining if reprocessing Bug: b/285021603 Change-Id: I4f48c26c39f7396a0ad615b87e524e6bcbdf938f --- .../callgraph/CallSiteInformation.java | 39 +++++++++++++++++-- .../r8/ir/optimize/MultiCallerInliner.java | 3 +- .../DefaultInliningReasonStrategy.java | 6 +-- .../SingleCallerBridgeStartupTest.java | 15 ++++++- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java index c2e13afb6e..234eeee5e3 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java @@ -13,6 +13,8 @@ import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.Sets; +import java.util.HashMap; +import java.util.Map; import java.util.Set; public abstract class CallSiteInformation { @@ -23,6 +25,14 @@ public abstract class CallSiteInformation { *

For pinned methods (methods kept through Proguard keep rules) this will always answer * false. */ + public abstract boolean hasSingleCallSite(ProgramMethod context, ProgramMethod method); + + /** + * Checks if the given method only has a single call without considering context. + * + *

For pinned methods (methods kept through Proguard keep rules) and methods that override a + * library method this always returns false. + */ public abstract boolean hasSingleCallSite(ProgramMethod method); public abstract boolean isMultiCallerInlineCandidate(ProgramMethod method); @@ -37,6 +47,11 @@ private static class EmptyCallSiteInformation extends CallSiteInformation { private static final EmptyCallSiteInformation EMPTY_INFO = new EmptyCallSiteInformation(); + @Override + public boolean hasSingleCallSite(ProgramMethod context, ProgramMethod method) { + return false; + } + @Override public boolean hasSingleCallSite(ProgramMethod method) { return false; @@ -55,7 +70,9 @@ public void unsetCallSiteInformation(ProgramMethod method) { static class CallGraphBasedCallSiteInformation extends CallSiteInformation { - private final Set singleCallerMethods = Sets.newIdentityHashSet(); + // Single callers track their calling context to ensure that the predicate is stable after + // inlining of the caller. + private final Map singleCallerMethods = new HashMap<>(); private final Set multiCallerInlineCandidates = Sets.newIdentityHashSet(); CallGraphBasedCallSiteInformation(AppView appView, CallGraph graph) { @@ -94,7 +111,10 @@ static class CallGraphBasedCallSiteInformation extends CallSiteInformation { int numberOfCallSites = node.getNumberOfCallSites(); if (numberOfCallSites == 1) { - singleCallerMethods.add(reference); + Node caller = node.getCallersWithDeterministicOrder().iterator().next(); + DexMethod existing = + singleCallerMethods.put(reference, caller.getMethod().getReference()); + assert existing == null; } else if (numberOfCallSites > 1) { multiCallerInlineCandidates.add(reference); } @@ -102,14 +122,25 @@ static class CallGraphBasedCallSiteInformation extends CallSiteInformation { } /** - * Checks if the given method only has a single call site. + * Checks if the given method only has a single call site with the given context. + * + *

For pinned methods (methods kept through Proguard keep rules) and methods that override a + * library method this always returns false. + */ + @Override + public boolean hasSingleCallSite(ProgramMethod context, ProgramMethod method) { + return singleCallerMethods.get(method.getReference()) == context.getReference(); + } + + /** + * Checks if the given method only has a single call without considering context. * *

For pinned methods (methods kept through Proguard keep rules) and methods that override a * library method this always returns false. */ @Override public boolean hasSingleCallSite(ProgramMethod method) { - return singleCallerMethods.contains(method.getReference()); + return singleCallerMethods.containsKey(method.getReference()); } /** diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java index 54c8f61a01..4b5f0c984c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java @@ -147,12 +147,11 @@ void recordCallEdgeForMultiCallerInlining( // We track up to n call sites, where n is the size of multiCallerInliningInstructionLimits. if (callers.size() > multiCallerInliningInstructionLimits.length) { stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining( - method, singleTarget, methodProcessor, callers); + singleTarget, methodProcessor, callers); } } private void stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining( - ProgramMethod method, ProgramMethod singleTarget, MethodProcessor methodProcessor, ProgramMethodMultiset callers) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java index 537150e519..1d3fb28c15 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java @@ -53,7 +53,7 @@ public Reason computeInliningReason( // program. return Reason.SIMPLE; } - if (isSingleCallerInliningTarget(target)) { + if (isSingleCallerInliningTarget(context, target)) { return Reason.SINGLE_CALLER; } if (isMultiCallerInlineCandidate(invoke, target, oracle, methodProcessor)) { @@ -64,8 +64,8 @@ public Reason computeInliningReason( return Reason.SIMPLE; } - private boolean isSingleCallerInliningTarget(ProgramMethod method) { - if (!callSiteInformation.hasSingleCallSite(method)) { + private boolean isSingleCallerInliningTarget(ProgramMethod context, ProgramMethod method) { + if (!callSiteInformation.hasSingleCallSite(context, method)) { return false; } if (appView.appInfo().isNeverInlineDueToSingleCallerMethod(method)) { diff --git a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java index 4a27e0fef6..1cb68de1c7 100644 --- a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java +++ b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java @@ -4,6 +4,9 @@ package com.android.tools.r8.startup; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -12,6 +15,7 @@ import com.android.tools.r8.startup.profile.ExternalStartupItem; import com.android.tools.r8.startup.profile.ExternalStartupMethod; import com.android.tools.r8.startup.utils.StartupTestingUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.junit.Test; @@ -55,9 +59,16 @@ public void test() throws Exception { (appView, inlinee, inliningDepth) -> inlinee.getMethodReference().equals(barMethod)) .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + // Assert that foo is not inlined. + ClassSubject A = inspector.clazz(A.class); + assertThat(A, isPresent()); + assertThat(A.uniqueMethodWithOriginalName("foo"), isPresent()); + }) .run(parameters.getRuntime(), Main.class) - // TODO(b/285021603): We should not fail here. - .assertFailureWithErrorThatThrows(NoSuchMethodError.class); + .assertSuccessWithOutputLines("A::foo", "A::foo"); } static class Main { From ac7ca7ea02277793be74e5384512245a535775e2 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 12:54:37 +0000 Subject: [PATCH 012/153] Revert "Keep singler caller inlinees after inlining if reprocessing" This reverts commit 5f69e7b6dccf24ebd760f75784d1784c2e36ecfa. Reason for revert: Inconsistent overloads Change-Id: Ic050083efa1e4f7fb7f746883c9c0204994a26e3 --- .../callgraph/CallSiteInformation.java | 39 ++----------------- .../r8/ir/optimize/MultiCallerInliner.java | 3 +- .../DefaultInliningReasonStrategy.java | 6 +-- .../SingleCallerBridgeStartupTest.java | 15 +------ 4 files changed, 11 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java index 234eeee5e3..c2e13afb6e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java @@ -13,8 +13,6 @@ import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.Sets; -import java.util.HashMap; -import java.util.Map; import java.util.Set; public abstract class CallSiteInformation { @@ -25,14 +23,6 @@ public abstract class CallSiteInformation { *

For pinned methods (methods kept through Proguard keep rules) this will always answer * false. */ - public abstract boolean hasSingleCallSite(ProgramMethod context, ProgramMethod method); - - /** - * Checks if the given method only has a single call without considering context. - * - *

For pinned methods (methods kept through Proguard keep rules) and methods that override a - * library method this always returns false. - */ public abstract boolean hasSingleCallSite(ProgramMethod method); public abstract boolean isMultiCallerInlineCandidate(ProgramMethod method); @@ -47,11 +37,6 @@ private static class EmptyCallSiteInformation extends CallSiteInformation { private static final EmptyCallSiteInformation EMPTY_INFO = new EmptyCallSiteInformation(); - @Override - public boolean hasSingleCallSite(ProgramMethod context, ProgramMethod method) { - return false; - } - @Override public boolean hasSingleCallSite(ProgramMethod method) { return false; @@ -70,9 +55,7 @@ public void unsetCallSiteInformation(ProgramMethod method) { static class CallGraphBasedCallSiteInformation extends CallSiteInformation { - // Single callers track their calling context to ensure that the predicate is stable after - // inlining of the caller. - private final Map singleCallerMethods = new HashMap<>(); + private final Set singleCallerMethods = Sets.newIdentityHashSet(); private final Set multiCallerInlineCandidates = Sets.newIdentityHashSet(); CallGraphBasedCallSiteInformation(AppView appView, CallGraph graph) { @@ -111,10 +94,7 @@ static class CallGraphBasedCallSiteInformation extends CallSiteInformation { int numberOfCallSites = node.getNumberOfCallSites(); if (numberOfCallSites == 1) { - Node caller = node.getCallersWithDeterministicOrder().iterator().next(); - DexMethod existing = - singleCallerMethods.put(reference, caller.getMethod().getReference()); - assert existing == null; + singleCallerMethods.add(reference); } else if (numberOfCallSites > 1) { multiCallerInlineCandidates.add(reference); } @@ -122,25 +102,14 @@ static class CallGraphBasedCallSiteInformation extends CallSiteInformation { } /** - * Checks if the given method only has a single call site with the given context. - * - *

For pinned methods (methods kept through Proguard keep rules) and methods that override a - * library method this always returns false. - */ - @Override - public boolean hasSingleCallSite(ProgramMethod context, ProgramMethod method) { - return singleCallerMethods.get(method.getReference()) == context.getReference(); - } - - /** - * Checks if the given method only has a single call without considering context. + * Checks if the given method only has a single call site. * *

For pinned methods (methods kept through Proguard keep rules) and methods that override a * library method this always returns false. */ @Override public boolean hasSingleCallSite(ProgramMethod method) { - return singleCallerMethods.containsKey(method.getReference()); + return singleCallerMethods.contains(method.getReference()); } /** diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java index 4b5f0c984c..54c8f61a01 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java @@ -147,11 +147,12 @@ void recordCallEdgeForMultiCallerInlining( // We track up to n call sites, where n is the size of multiCallerInliningInstructionLimits. if (callers.size() > multiCallerInliningInstructionLimits.length) { stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining( - singleTarget, methodProcessor, callers); + method, singleTarget, methodProcessor, callers); } } private void stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining( + ProgramMethod method, ProgramMethod singleTarget, MethodProcessor methodProcessor, ProgramMethodMultiset callers) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java index 1d3fb28c15..537150e519 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java @@ -53,7 +53,7 @@ public Reason computeInliningReason( // program. return Reason.SIMPLE; } - if (isSingleCallerInliningTarget(context, target)) { + if (isSingleCallerInliningTarget(target)) { return Reason.SINGLE_CALLER; } if (isMultiCallerInlineCandidate(invoke, target, oracle, methodProcessor)) { @@ -64,8 +64,8 @@ public Reason computeInliningReason( return Reason.SIMPLE; } - private boolean isSingleCallerInliningTarget(ProgramMethod context, ProgramMethod method) { - if (!callSiteInformation.hasSingleCallSite(context, method)) { + private boolean isSingleCallerInliningTarget(ProgramMethod method) { + if (!callSiteInformation.hasSingleCallSite(method)) { return false; } if (appView.appInfo().isNeverInlineDueToSingleCallerMethod(method)) { diff --git a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java index 1cb68de1c7..4a27e0fef6 100644 --- a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java +++ b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java @@ -4,9 +4,6 @@ package com.android.tools.r8.startup; -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.MatcherAssert.assertThat; - import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -15,7 +12,6 @@ import com.android.tools.r8.startup.profile.ExternalStartupItem; import com.android.tools.r8.startup.profile.ExternalStartupMethod; import com.android.tools.r8.startup.utils.StartupTestingUtils; -import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.junit.Test; @@ -59,16 +55,9 @@ public void test() throws Exception { (appView, inlinee, inliningDepth) -> inlinee.getMethodReference().equals(barMethod)) .setMinApi(parameters) - .compile() - .inspect( - inspector -> { - // Assert that foo is not inlined. - ClassSubject A = inspector.clazz(A.class); - assertThat(A, isPresent()); - assertThat(A.uniqueMethodWithOriginalName("foo"), isPresent()); - }) .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines("A::foo", "A::foo"); + // TODO(b/285021603): We should not fail here. + .assertFailureWithErrorThatThrows(NoSuchMethodError.class); } static class Main { From 42f5be67ba98224182a7c616fcfe4758ffa699d4 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 12:18:46 +0200 Subject: [PATCH 013/153] [Compose] Compose inlined outlines into outline callsites Bug: b/284925475 Change-Id: Ib5f55f95849eaac319e8d8102168d5d057e3e75a --- .../r8/naming/ClassNamingForNameMapper.java | 21 + .../tools/r8/naming/ComposingBuilder.java | 364 ++++++++++++++---- .../android/tools/r8/naming/MemberNaming.java | 4 + .../ComposeInlineFollowedByInlineTest.java | 4 +- .../ComposeInlineResidualQualifiedTest.java | 62 +++ .../ComposeMovedMethodTest.java | 2 +- .../ComposeMovedMethodWithPositionTest.java | 2 +- ...ComposeOutlineHavingInlineInlinedTest.java | 73 ++++ .../ComposeOutlineInlineTest.java | 18 +- ...tlineInlinedIntoOutlineAndInlinedTest.java | 84 ++++ 10 files changed, 542 insertions(+), 92 deletions(-) create mode 100644 src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java create mode 100644 src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java create mode 100644 src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlinedIntoOutlineAndInlinedTest.java diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java index 76a188c781..da66dfdec7 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java @@ -11,6 +11,7 @@ import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind; import com.android.tools.r8.naming.mappinginformation.MappingInformation; import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation; +import com.android.tools.r8.naming.mappinginformation.OutlineMappingInformation; import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation; import com.android.tools.r8.utils.ChainableStringConsumer; import com.android.tools.r8.utils.CollectionUtils; @@ -632,6 +633,15 @@ public List getRewriteFrameMappingInformation() MappingInformation::asRewriteFrameMappingInformation); } + public OutlineMappingInformation getOutlineMappingInformation() { + List outlineMappingInformation = + filter( + MappingInformation::isOutlineMappingInformation, + MappingInformation::asOutlineMappingInformation); + assert outlineMappingInformation.size() <= 1; + return outlineMappingInformation.isEmpty() ? null : outlineMappingInformation.get(0); + } + public int getOriginalLineNumber(int lineNumberAfterMinification) { if (minifiedRange == null) { // General mapping without concrete line numbers: "a() -> b" @@ -765,6 +775,11 @@ public List getAdditionalMappingInformation() { return Collections.unmodifiableList(additionalMappingInformation); } + public void setAdditionalMappingInformationInternal( + List mappingInformation) { + this.additionalMappingInformation = mappingInformation; + } + public MappedRange partitionOnMinifiedRange(Range minifiedRange) { if (minifiedRange.equals(this.minifiedRange)) { return this; @@ -784,5 +799,11 @@ public MappedRange partitionOnMinifiedRange(Range minifiedRange) { public boolean isOriginalRangePreamble() { return originalRange != null && originalRange.isPreamble(); } + + public MappedRange withMinifiedRange(Range newMinifiedRange) { + return newMinifiedRange.equals(minifiedRange) + ? this + : new MappedRange(newMinifiedRange, signature, originalRange, renamedName); + } } } diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index b272acadac..d87e0360bf 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -27,6 +27,7 @@ import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.ChainableStringConsumer; import com.android.tools.r8.utils.ConsumerUtils; +import com.android.tools.r8.utils.IntBox; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.Pair; @@ -42,12 +43,14 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.TreeMap; +import java.util.function.BiConsumer; import java.util.function.Consumer; public class ComposingBuilder { @@ -640,7 +643,10 @@ private void composeMethodNamings( if (composedInlineFrames.isEmpty()) { splitOnNewMinifiedRange( composeMappedRangesForMethod( - existingMappedRanges, mappedRange, computedOutlineInformation), + existingClassBuilder, + existingMappedRanges, + mappedRange, + computedOutlineInformation), Collections.emptyList(), newComposedInlineFrames::add); } else { @@ -649,7 +655,10 @@ private void composeMethodNamings( mappedRange.partitionOnMinifiedRange(composedInlineFrame.get(0).minifiedRange); splitOnNewMinifiedRange( composeMappedRangesForMethod( - existingMappedRanges, splitMappedRange, computedOutlineInformation), + existingClassBuilder, + existingMappedRanges, + splitMappedRange, + computedOutlineInformation), composedInlineFrame, newComposedInlineFrames::add); } @@ -662,86 +671,195 @@ private void composeMethodNamings( composedInlineFrames = Collections.emptyList(); } } - MappedRange lastComposedRange = ListUtils.last(composedRanges); - if (computedOutlineInformation.seenOutlineMappingInformation != null) { + // Check if we could have inlined an outline which is true if we see both an outline and + // call site to patch up. + if (!computedOutlineInformation.seenOutlineMappingInformation.isEmpty() + && !computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) { + Set outlineCallSitesToRemove = + Sets.newIdentityHashSet(); + Set outlinesToRemove = Sets.newIdentityHashSet(); + // We patch up all ranges from top to bottom with the invariant that all at a given + // index, all above have been updated correctly. We will do expansion of frames + // when separating out single minified lines, but we keep the outline information + // present such that we can fix them when seeing them later. + int composedRangeIndex = 0; + while (composedRangeIndex < composedRanges.size() - 1) { + MappedRange outline = composedRanges.get(composedRangeIndex++); + if (outline.isOutlineFrame() + && outline.minifiedRange.equals( + composedRanges.get(composedRangeIndex).minifiedRange)) { + // We should replace the inlined outline frame positions with the synthesized + // positions from the outline call site. + MappedRange outlineCallSite = composedRanges.get(composedRangeIndex); + if (outlineCallSite.getOutlineCallsiteInformation().size() != 1) { + // If we have an inlined outline it must be such that the outer frame is an + // outline callsite. + throw new MappingComposeException( + "Expected exactly one outline call site for a mapped range with signature '" + + outlineCallSite.getOriginalSignature() + + "'."); + } + OutlineCallsiteMappingInformation outlineCallSiteInformation = + outlineCallSite.getOutlineCallsiteInformation().get(0); + // The original positions in the outline callsite have been composed, so we have to + // find the existing mapped range and iterate the original positions for that range. + ComputedMappedRangeForOutline computedInformationForCallSite = + computedOutlineInformation.getComputedRange( + outlineCallSiteInformation, outlineCallSite); + if (computedInformationForCallSite == null) { + continue; + } + Map> mappedRangesForOutline = + new HashMap<>(outlineCallSiteInformation.getPositions().size()); + visitOutlineMappedPositions( + outlineCallSiteInformation, + computedInformationForCallSite + .current + .getOriginalSignature() + .asMethodSignature(), + mappedRangesForOutline::put); + List newComposedRanges = new ArrayList<>(); + // Copy all previous handled mapped ranges into a new list. + for (MappedRange previousMappedRanges : composedRanges) { + if (previousMappedRanges == outline) { + break; + } + newComposedRanges.add(previousMappedRanges); + } + // The original positions in the outline have been composed, so we have to find the + // existing mapped range and iterate the original positions for that range. + ComputedMappedRangeForOutline computedInformationForOutline = + computedOutlineInformation.getComputedRange( + outline.getOutlineMappingInformation(), outline); + if (computedInformationForOutline == null) { + continue; + } + // The outline could have additional inlined positions in it, but we should be + // guaranteed to find call site information on all original line numbers. We + // therefore iterate one by one and amend the subsequent outer frames as well. + MappedRange current = computedInformationForOutline.current; + int minifiedLine = outline.minifiedRange.from; + for (int originalLine = current.getOriginalRangeOrIdentity().from; + originalLine <= current.getOriginalRangeOrIdentity().to; + originalLine++) { + // If the outline is itself an inline frame it is bound to only have one original + // position and we can simply insert all inline frames on that position with the + // existing minified range. + Range newMinifiedRange = + outline.originalRange.isCardinal + ? outline.minifiedRange + : new Range(minifiedLine, minifiedLine); + List outlineMappedRanges = mappedRangesForOutline.get(originalLine); + if (outlineMappedRanges != null) { + outlineMappedRanges.forEach( + range -> { + if (range != ListUtils.last(outlineMappedRanges)) { + newComposedRanges.add( + new MappedRange( + newMinifiedRange, + range.getOriginalSignature().asMethodSignature(), + range.originalRange, + outlineCallSite.getRenamedName())); + } + }); + newComposedRanges.add( + new MappedRange( + newMinifiedRange, + outlineCallSite.getOriginalSignature().asMethodSignature(), + ListUtils.last(outlineMappedRanges).originalRange, + outlineCallSite.getRenamedName())); + } + for (int tailInlineFrameIndex = composedRangeIndex + 1; + tailInlineFrameIndex < composedRanges.size(); + tailInlineFrameIndex++) { + MappedRange originalMappedRange = composedRanges.get(tailInlineFrameIndex); + if (!originalMappedRange.minifiedRange.equals(outlineCallSite.minifiedRange)) { + break; + } + MappedRange newMappedRange = + originalMappedRange.withMinifiedRange(newMinifiedRange); + newMappedRange.setAdditionalMappingInformationInternal( + originalMappedRange.getAdditionalMappingInformation()); + newComposedRanges.add(newMappedRange); + } + minifiedLine++; + } + // We have patched up the the inlined outline and all subsequent inline frames + // (although some of the subsequent frames above could also be inlined outlines). We + // therefore need to copy the remaining frames. + boolean seenMinifiedRange = false; + for (MappedRange range : composedRanges) { + if (range.minifiedRange.equals(outline.minifiedRange)) { + seenMinifiedRange = true; + } else if (seenMinifiedRange) { + newComposedRanges.add(range); + } + } + composedRanges = newComposedRanges; + outlineCallSitesToRemove.add(outlineCallSiteInformation); + outlinesToRemove.add(outline.getOutlineMappingInformation()); + } + } + // If we removed any outlines or call site, remove the processing of them. + outlineCallSitesToRemove.forEach( + computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp::remove); + outlinesToRemove.forEach( + computedOutlineInformation.seenOutlineMappingInformation::remove); + } + if (!computedOutlineInformation.seenOutlineMappingInformation.isEmpty()) { + MappedRange lastComposedRange = ListUtils.last(composedRanges); current .getUpdateOutlineCallsiteInformation( committedPreviousClassBuilder.getRenamedName(), ListUtils.last(newMappedRanges).signature.getName(), lastComposedRange.getRenamedName()) .setNewMappedRanges(newMappedRanges); - lastComposedRange.addMappingInformation( - computedOutlineInformation.seenOutlineMappingInformation, - ConsumerUtils.emptyConsumer()); } if (!computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) { + MappedRange lastComposedRange = ListUtils.last(composedRanges); + List composedRangesFinal = composedRanges; // Outline positions are synthetic positions and they have no position in the residual // program. We therefore have to find the original positions and copy all inline frames // and amend the outermost frame with the residual signature and the next free position. List outlineCallSites = new ArrayList<>( - computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp); + computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.keySet()); outlineCallSites.sort(Comparator.comparing(mapping -> mapping.getOutline().toString())); - int firstAvailableRange = lastComposedRange.minifiedRange.to + 1; + IntBox firstAvailableRange = new IntBox(lastComposedRange.minifiedRange.to + 1); for (OutlineCallsiteMappingInformation outlineCallSite : outlineCallSites) { - Int2IntSortedMap positionMap = outlineCallSite.getPositions(); - MethodSignature originalSignature = - memberNaming.getOriginalSignature().asMethodSignature(); - ComposingClassBuilder existingClassBuilderForOutline = - getExistingClassBuilder(originalSignature); - if (existingClassBuilderForOutline == null) { - assert false; - continue; - } - SegmentTree> outlineSegmentTree = - existingClassBuilderForOutline.methodsWithPosition.get(originalSignature); - if (outlineSegmentTree == null) { - assert false; - continue; - } - Int2IntSortedMap newPositionMap = new Int2IntLinkedOpenHashMap(positionMap.size()); - for (Integer keyPosition : positionMap.keySet()) { - int keyPositionInt = keyPosition; - int originalDestination = positionMap.get(keyPositionInt); - ExistingMapping existingMapping = - computeExistingMapping(outlineSegmentTree.find(originalDestination)); - List mappedRangesForOutlinePosition = - existingMapping.getMappedRangesForPosition(originalDestination); - if (mappedRangesForOutlinePosition == null) { - assert false; - continue; - } - MappedRange outerMostOutlineFrame = ListUtils.last(mappedRangesForOutlinePosition); - assert outerMostOutlineFrame.minifiedRange.span() == 1; - Range newMinifiedRange = new Range(firstAvailableRange, firstAvailableRange); - for (MappedRange inlineMappedRangeInOutlinePosition : - mappedRangesForOutlinePosition) { - if (inlineMappedRangeInOutlinePosition != outerMostOutlineFrame) { - composedRanges.add( + Int2IntSortedMap newPositionMap = + new Int2IntLinkedOpenHashMap(outlineCallSite.getPositions().size()); + visitOutlineMappedPositions( + outlineCallSite, + memberNaming.getOriginalSignature().asMethodSignature(), + (originalPosition, mappedRangesForOutlinePosition) -> { + int newIndex = firstAvailableRange.getAndIncrement(); + Range newMinifiedRange = new Range(newIndex, newIndex); + MappedRange outerMostOutlineFrame = + ListUtils.last(mappedRangesForOutlinePosition); + for (MappedRange inlineMappedRangeInOutlinePosition : + mappedRangesForOutlinePosition) { + if (inlineMappedRangeInOutlinePosition != outerMostOutlineFrame) { + composedRangesFinal.add( + inlineMappedRangeInOutlinePosition.withMinifiedRange(newMinifiedRange)); + } + } + composedRangesFinal.add( new MappedRange( newMinifiedRange, - inlineMappedRangeInOutlinePosition.signature, - inlineMappedRangeInOutlinePosition.getOriginalRangeOrIdentity(), - inlineMappedRangeInOutlinePosition.getRenamedName())); - } - } - composedRanges.add( - new MappedRange( - newMinifiedRange, - lastComposedRange.signature, - outerMostOutlineFrame.originalRange, - lastComposedRange.getRenamedName())); - newPositionMap.put(keyPositionInt, firstAvailableRange); - firstAvailableRange = newMinifiedRange.to + 1; - } - outlineCallSite.setPositionsInternal(newPositionMap); + lastComposedRange.signature, + outerMostOutlineFrame.originalRange, + lastComposedRange.getRenamedName())); + newPositionMap.put((int) originalPosition, newIndex); + outlineCallSite.setPositionsInternal(newPositionMap); + }); } } MethodSignature residualSignature = memberNaming .computeResidualSignature(type -> inverseClassMapping.getOrDefault(type, type)) .asMethodSignature(); - if (lastComposedRange.minifiedRange != null) { + if (ListUtils.last(composedRanges).minifiedRange != null) { SegmentTree> listSegmentTree = methodsWithPosition.computeIfAbsent( residualSignature, ignored -> new SegmentTree<>(false)); @@ -749,12 +867,57 @@ private void composeMethodNamings( minified.getStartOrNoRangeFrom(), minified.getEndOrNoRangeFrom(), composedRanges); } else { assert composedRanges.size() == 1; - methodsWithoutPosition.put(residualSignature, lastComposedRange); + methodsWithoutPosition.put(residualSignature, ListUtils.last(composedRanges)); } } } } + private void visitOutlineMappedPositions( + OutlineCallsiteMappingInformation outlineCallSite, + MethodSignature originalSignature, + BiConsumer> outlinePositionConsumer) + throws MappingComposeException { + Int2IntSortedMap positionMap = outlineCallSite.getPositions(); + ComposingClassBuilder existingClassBuilder = getExistingClassBuilder(originalSignature); + if (existingClassBuilder == null) { + throw new MappingComposeException( + "Could not find builder with original signature '" + originalSignature + "'."); + } + SegmentTree> outlineSegmentTree = + existingClassBuilder.methodsWithPosition.get( + originalSignature.toUnqualifiedSignatureIfQualified().asMethodSignature()); + if (outlineSegmentTree == null) { + throw new MappingComposeException( + "Could not find method positions for original signature '" + originalSignature + "'."); + } + for (Integer keyPosition : positionMap.keySet()) { + int keyPositionInt = keyPosition; + int originalDestination = positionMap.get(keyPositionInt); + List mappedRanges = outlineSegmentTree.find(originalDestination); + if (mappedRanges == null) { + throw new MappingComposeException( + "Could not find ranges for outline position '" + + keyPosition + + "' with original signature '" + + originalSignature + + "'."); + } + ExistingMapping existingMapping = computeExistingMapping(mappedRanges); + List mappedRangesForOutlinePosition = + existingMapping.getMappedRangesForPosition(originalDestination); + if (mappedRangesForOutlinePosition == null) { + throw new MappingComposeException( + "Could not find ranges for outline position '" + + keyPosition + + "' with original signature '" + + originalSignature + + "'."); + } + outlinePositionConsumer.accept(keyPositionInt, mappedRangesForOutlinePosition); + } + } + private void splitOnNewMinifiedRange( List mappedRanges, List previouslyMapped, @@ -822,6 +985,7 @@ private void registerMappingInformationFromMappedRanges(MappedRange mappedRange) } private List composeMappedRangesForMethod( + ComposingClassBuilder existingClassBuilder, List existingRanges, MappedRange newRange, ComputedOutlineInformation computedOutlineInformation) @@ -834,7 +998,13 @@ private List composeMappedRangesForMethod( if (newRange.getOriginalRangeOrIdentity() == null) { MappedRange newComposedRange = new MappedRange( - newRange.minifiedRange, lastExistingRange.signature, null, newRange.renamedName); + newRange.minifiedRange, + potentiallyQualifySignature( + newRange.signature, + lastExistingRange.signature, + existingClassBuilder.getOriginalName()), + null, + newRange.renamedName); composeMappingInformation( newComposedRange.getAdditionalMappingInformation(), lastExistingRange.getAdditionalMappingInformation(), @@ -858,7 +1028,10 @@ private List composeMappedRangesForMethod( return Collections.singletonList( new MappedRange( newRange.minifiedRange, - lastExistingRange.signature, + potentiallyQualifySignature( + newRange.signature, + lastExistingRange.signature, + existingClassBuilder.getOriginalName()), lastExistingRange.originalRange != null && lastExistingRange.originalRange.span() == 1 ? lastExistingRange.originalRange @@ -882,6 +1055,7 @@ private List composeMappedRangesForMethod( // then we have a perfect mapping that we can translate directly. if (lastExistingMappedRange.minifiedRange.equals(newRange.getOriginalRangeOrIdentity())) { computeComposedMappedRange( + existingClassBuilder, newComposedRanges, newRange, existingMappedRanges, @@ -907,6 +1081,7 @@ private List composeMappedRangesForMethod( lastExistingMappedRangeForPosition.minifiedRange)) { // We have seen an existing range we have to compute a splitting for. computeComposedMappedRange( + existingClassBuilder, newComposedRanges, newRange, existingMappedRanges, @@ -949,6 +1124,7 @@ private List composeMappedRangesForMethod( } } computeComposedMappedRange( + existingClassBuilder, newComposedRanges, newRange, existingMappedRanges, @@ -1038,6 +1214,7 @@ public List getMappedRangesForPosition(int i) { } private void computeComposedMappedRange( + ComposingClassBuilder existingClassBuilder, List newComposedRanges, MappedRange newMappedRange, List existingMappedRanges, @@ -1071,7 +1248,10 @@ private void computeComposedMappedRange( MappedRange computedRange = new MappedRange( newMinifiedRange, - existingMappedRange.signature, + potentiallyQualifySignature( + newMappedRange.signature, + existingMappedRange.signature, + existingClassBuilder.getOriginalName()), newOriginalRange, newMappedRange.renamedName); List mappingInformationToCompose = new ArrayList<>(); @@ -1080,13 +1260,17 @@ private void computeComposedMappedRange( .forEach( info -> { if (info.isOutlineMappingInformation()) { - computedOutlineInformation.seenOutlineMappingInformation = - info.asOutlineMappingInformation(); - return; - } - if (info.isOutlineCallsiteInformation()) { - computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.add( - info.asOutlineCallsiteInformation()); + computedOutlineInformation + .seenOutlineMappingInformation + .computeIfAbsent( + info.asOutlineMappingInformation(), ignoreArgument(ArrayList::new)) + .add(new ComputedMappedRangeForOutline(newMappedRange, computedRange)); + } else if (info.isOutlineCallsiteInformation()) { + computedOutlineInformation + .outlineCallsiteMappingInformationToPatchUp + .computeIfAbsent( + info.asOutlineCallsiteInformation(), ignoreArgument(ArrayList::new)) + .add(new ComputedMappedRangeForOutline(newMappedRange, computedRange)); } mappingInformationToCompose.add(info); }); @@ -1229,6 +1413,14 @@ private void putAll( } } + private MethodSignature potentiallyQualifySignature( + MethodSignature newSignature, MethodSignature signature, String originalHolder) { + return !newSignature.isQualified() || signature.isQualified() + ? signature + : new MethodSignature( + originalHolder + "." + signature.name, signature.type, signature.parameters); + } + private static class RangeBuilder { private int start = Integer.MAX_VALUE; @@ -1256,9 +1448,35 @@ private int getEndOrNoRangeFrom() { } private static class ComputedOutlineInformation { - private final Set - outlineCallsiteMappingInformationToPatchUp = Sets.newIdentityHashSet(); - private OutlineMappingInformation seenOutlineMappingInformation = null; + private final Map> + outlineCallsiteMappingInformationToPatchUp = new IdentityHashMap<>(); + private final Map> + seenOutlineMappingInformation = new IdentityHashMap<>(); + + private ComputedMappedRangeForOutline getComputedRange( + MappingInformation outline, MappedRange current) { + List outlineMappingInformations = + outline.isOutlineMappingInformation() + ? seenOutlineMappingInformation.get(outline.asOutlineMappingInformation()) + : outlineCallsiteMappingInformationToPatchUp.get( + outline.asOutlineCallsiteInformation()); + if (outlineMappingInformations == null) { + return null; + } + return ListUtils.firstMatching( + outlineMappingInformations, + pair -> pair.composed.minifiedRange.contains(current.minifiedRange.from)); + } + } + + private static class ComputedMappedRangeForOutline { + private final MappedRange current; + private final MappedRange composed; + + private ComputedMappedRangeForOutline(MappedRange current, MappedRange composed) { + this.current = current; + this.composed = composed; + } } } } diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java index d1bfb78fc3..5ddf11e439 100644 --- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java +++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java @@ -510,6 +510,10 @@ public Signature toUnqualifiedSignature() { return new MethodSignature(toUnqualifiedName(), type, parameters); } + public Signature toUnqualifiedSignatureIfQualified() { + return isQualified() ? new MethodSignature(toUnqualifiedName(), type, parameters) : this; + } + @Override public Signature toQualifiedSignature(String holder) { return new MethodSignature(holder + "." + name, type, parameters); diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java index 04ffc1572f..dbfcdb53a7 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java @@ -58,9 +58,9 @@ public static TestParametersCollection data() { StringUtils.unixLines( "# {'id':'com.android.tools.r8.mapping','version':'experimental'}", "com.bar -> c:", - " 2:2:void inlinee1():43:43 -> y", + " 2:2:void com.foo.inlinee1():43:43 -> y", " 2:2:void foo.bar.baz.inlinee1():41 -> y", - " 2:2:void caller():40 -> y", + " 2:2:void com.foo.caller():40 -> y", " 2:2:void inlinee2():42:42 -> y", " 2:2:void foo.bar.baz.inlinee1():41 -> y", " 2:2:void caller():40 -> y", diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java new file mode 100644 index 0000000000..1912d84026 --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java @@ -0,0 +1,62 @@ +// Copyright (c) 2023, 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.mappingcompose; + +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ComposeInlineResidualQualifiedTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "com.foo -> A:", + " 1:3:void method1():42:44 -> x", + "com.bar -> B:", + " 4:6:void method2():104:106 -> x"); + private static final String mappingBar = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "B -> C:", + " 1:3:void A.x():2:2 -> y", + " 1:3:void x():4 -> y", + " 2:3:void x():5:6 -> y"); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "com.bar -> C:", + " 1:3:void com.foo.method1():43:43 -> y", + " 1:3:void method2():104:104 -> y", + " 2:3:void method2():105:106 -> y", + "com.foo -> A:"); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); + } +} diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodTest.java index 80c4decf3d..2991061a92 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodTest.java @@ -46,7 +46,7 @@ public static TestParametersCollection data() { "# {'id':'com.android.tools.r8.mapping','version':'experimental'}", "com.bar -> b:", " int some.other.Class.f1() -> h1", - " void f2() -> h2", + " void com.foo.f2() -> h2", "com.foo -> a:"); @Test diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodWithPositionTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodWithPositionTest.java index 68d2c86839..db17b8de99 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodWithPositionTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodWithPositionTest.java @@ -48,7 +48,7 @@ public static TestParametersCollection data() { "com.bar -> b:", " 12:14:int some.other.Class.f1():102:104 -> h11", " 15:16:int some.other.Class.f1():102:103 -> h12", - " 22:23:void f2():114:114 -> h2", + " 22:23:void com.foo.f2():114:114 -> h2", "com.foo -> a:"); @Test diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java new file mode 100644 index 0000000000..03e93e65b5 --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java @@ -0,0 +1,73 @@ +// Copyright (c) 2023, 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.mappingcompose; + +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +public class ComposeOutlineHavingInlineInlinedTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# { id: 'com.android.tools.r8.mapping', version: '2.2' }", + "outline.Class -> A:", + " 1:2:int some.inlinee():75:76 -> a", + " 1:2:int outline():0 -> a", + " # { 'id':'com.android.tools.r8.outline' }", + "outline.Callsite -> X:", + " 1:2:int outlineCaller(int):41:42 -> s", + " 3:3:int outlineCaller(int):0:0 -> s", + " # { 'id':'com.android.tools.r8.outlineCallsite'," + + "'positions': { '1': 9, '2': 10 }," + + "'outline':'La;a()I' }", + " 9:9:int outlineCaller(int):23 -> s", + " 10:10:int foo.bar.baz.outlineCaller(int):98:98 -> s", + " 10:10:int outlineCaller(int):24 -> s"); + private static final String mappingBar = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "X -> Y:", + " 1:1:int A.a():1:1 -> s", + " 1:1:int s(int):3 -> s", + " 2:4:int another.inline():102:104 -> s", + " 2:4:int A.a():2 -> s", + " 2:4:int s(int):3 -> s"); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "outline.Callsite -> Y:", + " 1:1:int some.inlinee():75:75 -> s", + " 1:1:int outlineCaller(int):23 -> s", + " 2:4:int another.inline():102:104 -> s", + " 2:4:int some.inlinee():76:76 -> s", + " 2:4:int foo.bar.baz.outlineCaller(int):98:98 -> s", + " 2:4:int outlineCaller(int):24 -> s", + "outline.Class -> A:"); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); + } +} diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java index 9203fed0d1..0c50e79c98 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java @@ -62,22 +62,10 @@ public static TestParametersCollection data() { "package.Class -> package.new_internal.Y:", "# {'id':'sourceFile','fileName':'FieldDefinition.java'}", " 1:6:void foo():21:26 -> b", - // TODO(b/284925475): We should put in the inline positions here instead of a reference to - // the outline - " 7:8:long package.Int2IntLinkedOpenHashMap$$InternalSyntheticOutline$HASH$0" - + ".m(long,long,long):0:1 -> b", - // TODO(b/284925475): This is not synthesized. - " # {'id':'com.android.tools.r8.synthesized'}", - " 7:8:void foo():0:0 -> b", - " # {'id':'com.android.tools.r8.outlineCallsite'," - + "'positions':{'1':11,'2':12}," - + "'outline':'Lpackage/internal/X;a(JJJ)J'}", + " 7:7:void inlineeInOutline():1337:1337 -> b", + " 7:7:void foo():42 -> b", + " 8:8:void foo():44:44 -> b", " 9:10:void foo():38:39 -> b", - // TODO(b/284925475): This is not an outline or outline call site. - " # {'id':'com.android.tools.r8.outline'}", - " 11:11:void inlineeInOutline():1337:1337 -> a", - " 11:11:void foo():42 -> b", - " 12:12:void foo():44:44 -> b", "package.Class$$ExternalSyntheticOutline0 -> package.internal.X:", "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}", "# {'id':'com.android.tools.r8.synthesized'}"); diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlinedIntoOutlineAndInlinedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlinedIntoOutlineAndInlinedTest.java new file mode 100644 index 0000000000..9dc41ea813 --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlinedIntoOutlineAndInlinedTest.java @@ -0,0 +1,84 @@ +// Copyright (c) 2023, 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.mappingcompose; + +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +public class ComposeOutlineInlinedIntoOutlineAndInlinedTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# { id: 'com.android.tools.r8.mapping', version: '2.2' }", + "Outline1 -> A:", + " 1:2:int outline1():0:1 -> a", + " # { 'id':'com.android.tools.r8.outline' }", + "Outline2 -> B:", + " 1:2:int outline2():0:1 -> a", + " # { 'id':'com.android.tools.r8.outline' }", + "outline.Callsite1 -> X:", + " 1:2:int caller1(int):41:42 -> s", + " 3:3:int caller1(int):0:0 -> s", + " # { 'id':'com.android.tools.r8.outlineCallsite'," + + "'positions': { '1': 9, '2': 10 }," + + "'outline':'La;a()I' }", + " 4:6:int caller1(int):48:49 -> s", + " 9:9:int caller1(int):23 -> s", + " 10:10:int caller1(int):24 -> s", + "outline.Callsite2 -> Y:", + " 1:1:int caller2(int):0:0 -> s", + " # { 'id':'com.android.tools.r8.outlineCallsite'," + + "'positions': { '1': 2, '2': 3 }," + + "'outline':'La;a()I' }", + " 2:2:int caller2(int):23:23 -> s", + " 3:3:int caller2(int):24:24 -> s"); + private static final String mappingBar = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "Y -> Z:", + " 1:2:int A.a():1:2 -> a", + " 1:2:int X.s(int):3 -> a", + " 1:2:int B.a():1 -> a", + " 1:2:int s(int):1 -> a", + " 3:3:int B.a():2:2 -> a", + " 3:3:int s(int):1 -> a"); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "Outline1 -> A:", + "Outline2 -> B:", + "outline.Callsite1 -> X:", + "outline.Callsite2 -> Z:", + " 1:1:int outline.Callsite1.caller1(int):23 -> a", + " 1:1:int caller2(int):23:23 -> a", + " 2:2:int outline.Callsite1.caller1(int):24 -> a", + " 2:2:int caller2(int):23:23 -> a", + " 3:3:int caller2(int):24:24 -> a"); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); + } +} From b4a878f10cf4b76f305c167e9d6cbbb5856c11d6 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 6 Jun 2023 17:35:28 +0200 Subject: [PATCH 014/153] Update .gitignore to not track third_party/dependencies Change-Id: Ief0ed0f54425513aebe9800e922116ed51f93ff5 --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index b8c394a03b..90f5857921 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,10 @@ third_party/dart-sdk third_party/dart-sdk.tar.gz third_party/ddmlib third_party/ddmlib.tar.gz +third_party/dependencies/ +third_party/dependencies.tar.gz +third_party/dependencies_new/ +third_party/dependencies_new.tar.gz third_party/desugar/desugar_*.tar.gz third_party/desugar/desugar_*/ third_party/framework From 349474a6dbd2949d1f8baaa865fbff5aef7acd1b Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Wed, 7 Jun 2023 08:50:00 +0200 Subject: [PATCH 015/153] Refactor computation of non program methods for tree fixing Change-Id: I0227f6fafdda31e861dce71ed99a9a6584ab7808 --- .../tools/r8/graph/ClassDefinition.java | 7 +++ .../com/android/tools/r8/graph/DexClass.java | 15 +++++ .../r8/graph/fixup/ConcurrentMethodFixup.java | 61 +++++-------------- .../ArgumentPropagatorProgramOptimizer.java | 40 +++--------- ...ConcurrentNonProgramMethodsCollection.java | 27 ++++++++ .../utils/NonProgramMethodsCollection.java | 60 ++++++++++++++++++ 6 files changed, 135 insertions(+), 75 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/optimize/utils/ConcurrentNonProgramMethodsCollection.java create mode 100644 src/main/java/com/android/tools/r8/optimize/utils/NonProgramMethodsCollection.java diff --git a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java index 589dd76de1..a021545367 100644 --- a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java +++ b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java @@ -5,6 +5,8 @@ package com.android.tools.r8.graph; import com.android.tools.r8.references.ClassReference; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; import java.util.function.Consumer; public interface ClassDefinition extends Definition { @@ -15,6 +17,11 @@ public interface ClassDefinition extends Definition { void forEachClassMethod(Consumer consumer); + void forEachImmediateSuperClassMatching( + DexDefinitionSupplier definitions, + BiPredicate predicate, + BiConsumer consumer); + MethodCollection getMethodCollection(); ClassReference getClassReference(); diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index 1d7c8cf607..30909c5e30 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java @@ -37,6 +37,7 @@ import java.util.ListIterator; import java.util.Set; import java.util.function.BiConsumer; +import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -803,6 +804,20 @@ public void forEachImmediateInterface(Consumer fn) { } } + @Override + public void forEachImmediateSuperClassMatching( + DexDefinitionSupplier definitions, + BiPredicate predicate, + BiConsumer consumer) { + forEachImmediateSupertype( + supertype -> { + DexClass superclass = definitions.definitionFor(supertype); + if (predicate.test(supertype, superclass)) { + consumer.accept(supertype, superclass); + } + }); + } + public void forEachImmediateSupertype(Consumer fn) { if (superType != null) { fn.accept(superType); diff --git a/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java index 8a4c7bdbd2..81d780f33c 100644 --- a/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java +++ b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java @@ -9,7 +9,6 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ClasspathOrLibraryClass; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.graph.DexProgramClass; @@ -17,33 +16,33 @@ import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph; +import com.android.tools.r8.optimize.utils.ConcurrentNonProgramMethodsCollection; +import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.KeepMethodInfo; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; -import com.android.tools.r8.utils.collections.DexMethodSignatureSet; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; public class ConcurrentMethodFixup { private final AppView appView; - private final Map nonProgramVirtualMethods = - new ConcurrentHashMap<>(); + private final NonProgramMethodsCollection nonProgramVirtualMethods; private final ProgramClassFixer programClassFixer; public ConcurrentMethodFixup( AppView appView, ProgramClassFixer programClassFixer) { this.appView = appView; + this.nonProgramVirtualMethods = + ConcurrentNonProgramMethodsCollection.createVirtualMethodsCollection(appView); this.programClassFixer = programClassFixer; } @@ -173,47 +172,19 @@ private void reserveComponentPinnedAndInterfaceMethodSignatures( componentSignatures.put(method.getMethodSignature(), method.getMethodSignature()); } }); - clazz.forEachImmediateSupertype( - supertype -> { - DexClass superclass = appView.definitionFor(supertype); - if (superclass != null - && !superclass.isProgramClass() - && seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass())) { - for (DexMethodSignature vMethod : - getOrComputeNonProgramVirtualMethods(superclass.asClasspathOrLibraryClass())) { - componentSignatures.put(vMethod, vMethod); - } + clazz.forEachImmediateSuperClassMatching( + appView, + (supertype, superclass) -> + superclass != null + && !superclass.isProgramClass() + && seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass()), + (supertype, superclass) -> { + for (DexMethodSignature vMethod : + nonProgramVirtualMethods.getOrComputeNonProgramMethods( + superclass.asClasspathOrLibraryClass())) { + componentSignatures.put(vMethod, vMethod); } }); } } - - private DexMethodSignatureSet getOrComputeNonProgramVirtualMethods( - ClasspathOrLibraryClass clazz) { - DexMethodSignatureSet libraryMethodsOnClass = nonProgramVirtualMethods.get(clazz); - if (libraryMethodsOnClass != null) { - return libraryMethodsOnClass; - } - return computeNonProgramVirtualMethods(clazz); - } - - private DexMethodSignatureSet computeNonProgramVirtualMethods( - ClasspathOrLibraryClass classpathOrLibraryClass) { - DexClass clazz = classpathOrLibraryClass.asDexClass(); - DexMethodSignatureSet libraryMethodsOnClass = DexMethodSignatureSet.create(); - clazz.forEachImmediateSupertype( - supertype -> { - DexClass superclass = appView.definitionFor(supertype); - if (superclass != null) { - assert !superclass.isProgramClass(); - libraryMethodsOnClass.addAll( - getOrComputeNonProgramVirtualMethods(superclass.asClasspathOrLibraryClass())); - } - }); - clazz.forEachClassMethodMatching( - DexEncodedMethod::belongsToVirtualPool, - method -> libraryMethodsOnClass.add(method.getMethodSignature())); - nonProgramVirtualMethods.put(classpathOrLibraryClass, libraryMethodsOnClass); - return libraryMethodsOnClass; - } } diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java index 1a2eaa60d2..94a7d22c44 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java @@ -9,7 +9,6 @@ import com.android.tools.r8.contexts.CompilationContext.ProcessorContext; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; @@ -36,6 +35,8 @@ import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo; import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorGraphLens.Builder; import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils; +import com.android.tools.r8.optimize.utils.ConcurrentNonProgramMethodsCollection; +import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.KeepFieldInfo; import com.android.tools.r8.shaking.KeepMethodInfo; @@ -73,7 +74,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.function.Consumer; @@ -173,9 +173,7 @@ public boolean equals(Object obj) { private final AppView appView; private final ImmediateProgramSubtypingInfo immediateSubtypingInfo; private final Map, DexMethodSignatureSet> interfaceDispatchOutsideProgram; - - private final Map libraryVirtualMethods = - new ConcurrentHashMap<>(); + private final NonProgramMethodsCollection nonProgramMethodsCollection; public ArgumentPropagatorProgramOptimizer( AppView appView, @@ -184,6 +182,8 @@ public ArgumentPropagatorProgramOptimizer( this.appView = appView; this.immediateSubtypingInfo = immediateSubtypingInfo; this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram; + this.nonProgramMethodsCollection = + ConcurrentNonProgramMethodsCollection.createVirtualMethodsCollection(appView); } public ArgumentPropagatorGraphLens run( @@ -221,28 +221,6 @@ public ArgumentPropagatorGraphLens run( return graphLens; } - private DexMethodSignatureSet getOrComputeLibraryVirtualMethods(DexClass clazz) { - DexMethodSignatureSet libraryMethodsOnClass = libraryVirtualMethods.get(clazz); - if (libraryMethodsOnClass != null) { - return libraryMethodsOnClass; - } - return computeLibraryVirtualMethods(clazz); - } - - private DexMethodSignatureSet computeLibraryVirtualMethods(DexClass clazz) { - DexMethodSignatureSet libraryMethodsOnClass = DexMethodSignatureSet.create(); - immediateSubtypingInfo.forEachImmediateSuperClassMatching( - clazz, - (supertype, superclass) -> superclass != null, - (supertype, superclass) -> - libraryMethodsOnClass.addAll(getOrComputeLibraryVirtualMethods(superclass))); - clazz.forEachClassMethodMatching( - DexEncodedMethod::belongsToVirtualPool, - method -> libraryMethodsOnClass.add(method.getMethodSignature())); - libraryVirtualMethods.put(clazz, libraryMethodsOnClass); - return libraryMethodsOnClass; - } - public class StronglyConnectedComponentOptimizer { private final DexItemFactory dexItemFactory = appView.dexItemFactory(); @@ -325,7 +303,7 @@ private ArgumentPropagatorGraphLens.Builder optimize( private void reservePinnedMethodSignatures( Set stronglyConnectedProgramClasses) { DexMethodSignatureSet pinnedMethodSignatures = DexMethodSignatureSet.create(); - Set seenLibraryClasses = Sets.newIdentityHashSet(); + Set seenNonProgramClasses = Sets.newIdentityHashSet(); for (DexProgramClass clazz : stronglyConnectedProgramClasses) { clazz.forEachProgramMethodMatching( method -> !method.isInstanceInitializer(), @@ -341,9 +319,11 @@ private void reservePinnedMethodSignatures( (supertype, superclass) -> superclass != null && !superclass.isProgramClass() - && seenLibraryClasses.add(superclass), + && seenNonProgramClasses.add(superclass), (supertype, superclass) -> - pinnedMethodSignatures.addAll(getOrComputeLibraryVirtualMethods(superclass))); + pinnedMethodSignatures.addAll( + nonProgramMethodsCollection.getOrComputeNonProgramMethods( + superclass.asClasspathOrLibraryClass()))); } pinnedMethodSignatures.forEach( signature -> diff --git a/src/main/java/com/android/tools/r8/optimize/utils/ConcurrentNonProgramMethodsCollection.java b/src/main/java/com/android/tools/r8/optimize/utils/ConcurrentNonProgramMethodsCollection.java new file mode 100644 index 0000000000..0d5e0ebd5c --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/utils/ConcurrentNonProgramMethodsCollection.java @@ -0,0 +1,27 @@ +// Copyright (c) 2023, 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.optimize.utils; + +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class ConcurrentNonProgramMethodsCollection extends NonProgramMethodsCollection { + + ConcurrentNonProgramMethodsCollection(AppView appView) { + super(appView, new ConcurrentHashMap<>()); + } + + public static ConcurrentNonProgramMethodsCollection createVirtualMethodsCollection( + AppView appView) { + return new ConcurrentNonProgramMethodsCollection(appView) { + @Override + public boolean test(DexClassAndMethod method) { + return method.getAccessFlags().belongsToVirtualPool(); + } + }; + } +} diff --git a/src/main/java/com/android/tools/r8/optimize/utils/NonProgramMethodsCollection.java b/src/main/java/com/android/tools/r8/optimize/utils/NonProgramMethodsCollection.java new file mode 100644 index 0000000000..da93905569 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/utils/NonProgramMethodsCollection.java @@ -0,0 +1,60 @@ +// Copyright (c) 2023, 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.optimize.utils; + +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ClasspathOrLibraryClass; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.utils.collections.DexMethodSignatureSet; +import java.util.Map; + +public abstract class NonProgramMethodsCollection { + + private final AppView appView; + private final Map nonProgramMethods; + + NonProgramMethodsCollection( + AppView appView, + Map nonProgramMethods) { + this.appView = appView; + this.nonProgramMethods = nonProgramMethods; + } + + public DexMethodSignatureSet getOrComputeNonProgramMethods(ClasspathOrLibraryClass clazz) { + return getOrComputeNonProgramMethods(clazz.asDexClass()); + } + + // Parameter is typed as DexClass to account for program classes above classpath or library + // classes. + private DexMethodSignatureSet getOrComputeNonProgramMethods(DexClass clazz) { + DexMethodSignatureSet nonProgramMethodsOnClass = nonProgramMethods.get(clazz); + return nonProgramMethodsOnClass != null + ? nonProgramMethodsOnClass + : computeNonProgramMethods(clazz); + } + + private DexMethodSignatureSet computeNonProgramMethods(DexClass clazz) { + DexMethodSignatureSet nonProgramMethodsOnClass = DexMethodSignatureSet.create(); + clazz.forEachImmediateSuperClassMatching( + appView, + (supertype, superclass) -> superclass != null, + (supertype, superclass) -> + nonProgramMethodsOnClass.addAll(getOrComputeNonProgramMethods(superclass))); + if (!clazz.isProgramClass()) { + clazz.forEachClassMethod( + method -> { + if (test(method)) { + nonProgramMethodsOnClass.add(method.getMethodSignature()); + } + }); + } + nonProgramMethods.put(clazz, nonProgramMethodsOnClass); + return nonProgramMethodsOnClass; + } + + public abstract boolean test(DexClassAndMethod method); +} From a6392810a3ff6c54731aba472e450ec0d9eb155f Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Wed, 7 Jun 2023 08:50:58 +0200 Subject: [PATCH 016/153] Use a signature bimap in ConcurrentMethodFixup Change-Id: I50b57777cfaa5a7299c9cf2c0fc3be770fd9dbd8 --- .../r8/graph/fixup/ConcurrentMethodFixup.java | 25 +++++++++---------- .../r8/graph/fixup/MethodNamingUtility.java | 5 ++-- .../collections/DexMethodSignatureBiMap.java | 14 +++++++++++ .../collections/DexMethodSignatureMap.java | 13 ++++++++-- 4 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureBiMap.java diff --git a/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java index 81d780f33c..ce17027730 100644 --- a/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java +++ b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java @@ -22,6 +22,7 @@ import com.android.tools.r8.shaking.KeepMethodInfo; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; +import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Sets; @@ -76,7 +77,8 @@ public interface ProgramClassFixer { private void processConnectedProgramComponents(Set classes) { List sorted = new ArrayList<>(classes); sorted.sort(Comparator.comparing(DexClass::getType)); - BiMap componentSignatures = HashBiMap.create(); + DexMethodSignatureBiMap componentSignatures = + new DexMethodSignatureBiMap<>(); // 1) Reserve all library overrides and pinned virtual methods. reserveComponentPinnedAndInterfaceMethodSignatures(sorted, componentSignatures); @@ -104,7 +106,7 @@ private void processConnectedProgramComponents(Set classes) { private void processClass( DexProgramClass clazz, Set processedClasses, - BiMap componentSignatures) { + DexMethodSignatureBiMap componentSignatures) { assert !clazz.isInterface(); if (!processedClasses.add(clazz)) { return; @@ -121,7 +123,7 @@ private void processClass( private void processInterface( DexProgramClass clazz, Set processedInterfaces, - BiMap componentSignatures) { + DexMethodSignatureBiMap componentSignatures) { assert clazz.isInterface(); if (!processedInterfaces.add(clazz)) { return; @@ -147,7 +149,7 @@ private boolean shouldReserveAsPinned(ProgramMethod method) { } private MethodNamingUtility createMethodNamingUtility( - BiMap inheritedSignatures, DexProgramClass clazz) { + DexMethodSignatureBiMap componentSignatures, DexProgramClass clazz) { BiMap localSignatures = HashBiMap.create(); clazz.forEachProgramInstanceInitializer( method -> { @@ -155,12 +157,12 @@ private MethodNamingUtility createMethodNamingUtility( localSignatures.put(method.getReference(), method.getReference()); } }); - return new MethodNamingUtility(appView.dexItemFactory(), inheritedSignatures, localSignatures); + return new MethodNamingUtility(appView.dexItemFactory(), componentSignatures, localSignatures); } private void reserveComponentPinnedAndInterfaceMethodSignatures( List stronglyConnectedProgramClasses, - BiMap componentSignatures) { + DexMethodSignatureBiMap componentSignatures) { Set seenNonProgramClasses = Sets.newIdentityHashSet(); for (DexProgramClass clazz : stronglyConnectedProgramClasses) { // If a private or static method is pinned, we need to reserve the mapping to avoid creating @@ -178,13 +180,10 @@ private void reserveComponentPinnedAndInterfaceMethodSignatures( superclass != null && !superclass.isProgramClass() && seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass()), - (supertype, superclass) -> { - for (DexMethodSignature vMethod : - nonProgramVirtualMethods.getOrComputeNonProgramMethods( - superclass.asClasspathOrLibraryClass())) { - componentSignatures.put(vMethod, vMethod); - } - }); + (supertype, superclass) -> + componentSignatures.putAllToIdentity( + nonProgramVirtualMethods.getOrComputeNonProgramMethods( + superclass.asClasspathOrLibraryClass()))); } } } diff --git a/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java b/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java index 977e01cb47..688853d58e 100644 --- a/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java +++ b/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java @@ -10,18 +10,19 @@ import com.android.tools.r8.graph.DexMethodSignature; import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap; import com.google.common.collect.BiMap; import java.util.function.BiConsumer; public class MethodNamingUtility { private final DexItemFactory factory; - private final BiMap inheritedSignatures; + private final DexMethodSignatureBiMap inheritedSignatures; private final BiMap localSignatures; public MethodNamingUtility( DexItemFactory factory, - BiMap inheritedSignatures, + DexMethodSignatureBiMap inheritedSignatures, BiMap localSignatures) { this.factory = factory; this.inheritedSignatures = inheritedSignatures; diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureBiMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureBiMap.java new file mode 100644 index 0000000000..64ce1ec2a1 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureBiMap.java @@ -0,0 +1,14 @@ +// Copyright (c) 2023, 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.utils.collections; + +import com.google.common.collect.HashBiMap; + +public class DexMethodSignatureBiMap extends DexMethodSignatureMap { + + public DexMethodSignatureBiMap() { + super(HashBiMap.create()); + } +} diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java index 84d1cae4e9..5289f92d38 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java @@ -20,7 +20,7 @@ public class DexMethodSignatureMap implements Map { private final Map backing; - private DexMethodSignatureMap(Map backing) { + DexMethodSignatureMap(Map backing) { this.backing = backing; } @@ -189,7 +189,16 @@ public T remove(Object o) { } @Override - public void putAll(Map m) {} + public void putAll(Map map) { + map.forEach(this::put); + } + + @SuppressWarnings("unchecked") + public void putAllToIdentity(Collection signatures) { + for (DexMethodSignature signature : signatures) { + put(signature, (T) signature); + } + } public T remove(DexMethodSignature signature) { return backing.remove(signature); From c1ae3f1b061fefae6e8ab1d0a10168de3d039392 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Wed, 7 Jun 2023 08:51:08 +0200 Subject: [PATCH 017/153] Misc. utils for new access modifier Change-Id: I4dea7ab9efac3c80f72a9f55c0127508c44533cb --- .../com/android/tools/r8/graph/AccessFlags.java | 8 ++++++++ .../java/com/android/tools/r8/graph/AppView.java | 10 ++++++++++ .../r8/graph/ImmediateProgramSubtypingInfo.java | 13 ++++++++++++- .../tools/r8/graph/MethodArrayBacking.java | 4 ++-- .../android/tools/r8/graph/MethodCollection.java | 7 +++++++ .../com/android/tools/r8/utils/ListUtils.java | 2 +- .../utils/collections/DexMethodSignatureMap.java | 15 +++++++++++++++ 7 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java index ee8668045d..54fecaa166 100644 --- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java @@ -236,6 +236,10 @@ public T demoteFromFinal() { return self(); } + public boolean isPromotedFromPrivateToPublic() { + return isDemoted(Constants.ACC_PRIVATE) && isPromoted(Constants.ACC_PUBLIC); + } + public boolean isPromotedToPublic() { return isPromoted(Constants.ACC_PUBLIC); } @@ -277,6 +281,10 @@ protected void unset(int flag) { modifiedFlags &= ~flag; } + protected boolean isDemoted(int flag) { + return wasSet(flag) && !isSet(flag); + } + protected boolean isPromoted(int flag) { return !wasSet(flag) && isSet(flag); } diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index aff2b318fe..4eff3178ae 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -41,6 +41,7 @@ import com.android.tools.r8.shaking.AssumeInfoCollection; import com.android.tools.r8.shaking.KeepClassInfo; import com.android.tools.r8.shaking.KeepFieldInfo; +import com.android.tools.r8.shaking.KeepInfo; import com.android.tools.r8.shaking.KeepInfoCollection; import com.android.tools.r8.shaking.KeepMethodInfo; import com.android.tools.r8.shaking.LibraryModeledPredicate; @@ -679,6 +680,15 @@ public KeepInfoCollection getKeepInfo() { return keepInfo; } + public KeepInfo getKeepInfo(ProgramDefinition definition) { + return definition + .getReference() + .apply( + clazz -> getKeepInfo(definition.asProgramClass()), + field -> getKeepInfo(definition.asProgramField()), + method -> getKeepInfo(definition.asProgramMethod())); + } + public KeepClassInfo getKeepInfo(DexProgramClass clazz) { return getKeepInfo().getClassInfo(clazz); } diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java index 521d6c7492..8b9a38b841 100644 --- a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java +++ b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java @@ -9,6 +9,7 @@ import static com.google.common.base.Predicates.alwaysTrue; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; @@ -32,8 +33,18 @@ private ImmediateProgramSubtypingInfo( public static ImmediateProgramSubtypingInfo create( AppView appView) { + return internalCreate(appView, appView.appInfo().classes()); + } + + public static ImmediateProgramSubtypingInfo createWithDeterministicOrder( + AppView appView) { + return internalCreate(appView, appView.appInfo().classesWithDeterministicOrder()); + } + + private static ImmediateProgramSubtypingInfo internalCreate( + AppView appView, Collection classes) { Map> immediateSubtypes = new IdentityHashMap<>(); - for (DexProgramClass clazz : appView.appInfo().classes()) { + for (DexProgramClass clazz : classes) { clazz.forEachImmediateSupertype( supertype -> { DexProgramClass superclass = asProgramClassOrNull(appView.definitionFor(supertype)); diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java index 24c8b0a992..77d8fbdcb4 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java +++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java @@ -372,7 +372,7 @@ private List internalReplaceDirectMethods( DexEncodedMethod method = directMethods[i]; DexEncodedMethod newMethod = replacement.apply(method); assert newMethod != null; - if (method != newMethod) { + if (method != newMethod || !method.belongsToDirectPool()) { if (belongsToDirectPool(newMethod)) { directMethods[i] = newMethod; } else { @@ -404,7 +404,7 @@ private List internalReplaceVirtualMethods( for (int i = 0; i < virtualMethods.length; i++) { DexEncodedMethod method = virtualMethods[i]; DexEncodedMethod newMethod = replacement.apply(method); - if (method != newMethod) { + if (method != newMethod || !method.belongsToVirtualPool()) { if (belongsToVirtualPool(newMethod)) { virtualMethods[i] = newMethod; } else { diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java index a31e669448..47d8a0229f 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java +++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java @@ -269,6 +269,13 @@ public void replaceMethods(Function replacem backing.replaceMethods(replacement); } + @SuppressWarnings("unchecked") + public void replaceClassAndMethods( + Function replacement) { + assert holder.isProgramClass(); + replaceMethods(method -> replacement.apply((T) DexClassAndMethod.create(holder, method))); + } + public void replaceDirectMethods(Function replacement) { resetDirectMethodCaches(); backing.replaceDirectMethods(replacement); diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java index c998cf6764..d09475d462 100644 --- a/src/main/java/com/android/tools/r8/utils/ListUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java @@ -287,7 +287,7 @@ public interface ReferenceAndIntConsumer { void accept(T item, int index); } - public static List sort(List items, Comparator comparator) { + public static List sort(Collection items, Comparator comparator) { List sorted = new ArrayList<>(items); sorted.sort(comparator); return sorted; diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java index 5289f92d38..bbf5afb415 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java @@ -4,10 +4,12 @@ package com.android.tools.r8.utils.collections; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexMethodSignature; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -28,10 +30,18 @@ public static DexMethodSignatureMap create() { return new DexMethodSignatureMap<>(new HashMap<>()); } + public static DexMethodSignatureMap create(DexMethodSignatureMap map) { + return new DexMethodSignatureMap<>(new HashMap<>(map.backing)); + } + public static DexMethodSignatureMap createLinked() { return new DexMethodSignatureMap<>(new LinkedHashMap<>()); } + public static DexMethodSignatureMap empty() { + return new DexMethodSignatureMap<>(Collections.emptyMap()); + } + @Override public T put(DexMethodSignature signature, T value) { return backing.put(signature, value); @@ -100,6 +110,11 @@ public T replace(DexMethodSignature key, T value) { return backing.replace(key, value); } + public T computeIfAbsent( + DexClassAndMethod key, Function mappingFunction) { + return computeIfAbsent(key.getMethodSignature(), mappingFunction); + } + @Override public T computeIfAbsent( DexMethodSignature key, Function mappingFunction) { From 2918a9101ea73f51a8193c63d2c9df556dbd5e9c Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Wed, 7 Jun 2023 08:51:18 +0200 Subject: [PATCH 018/153] Fix method-is-in-profile check in vertical class merger Change-Id: I619f5a89a5a2f6a6aae5cded2c57a4384a753864 --- .../java/com/android/tools/r8/shaking/VerticalClassMerger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index b1673ba7d0..ead40635f9 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java @@ -633,7 +633,7 @@ public VerticalClassMergerGraphLens run() { if (!profileCollectionAdditions.isNop()) { for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) { profileCollectionAdditions.applyIfContextIsInProfile( - synthesizedBridge.originalMethod, + lens.getPreviousMethodSignature(synthesizedBridge.method), additionsBuilder -> additionsBuilder.addRule(synthesizedBridge.method)); } } From 0669fceca3b9857326338b3d3c6d94506cbca0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Wed, 7 Jun 2023 09:37:29 +0200 Subject: [PATCH 019/153] Extend Binop rewriter - associative property for add, mul, and, or, xor - shift combination - sub combination - mixed add/sub combination Change-Id: I68b1fa1d7cab4f9787b368055acd68703354183b --- .../ir/conversion/passes/BinopRewriter.java | 341 ++++++++++++-- .../tools/r8/ir/AssociativeIntTest.java | 424 ++++++++++++++++++ .../tools/r8/ir/AssociativeLongTest.java | 424 ++++++++++++++++++ .../tools/r8/ir/IdentityAbsorbingTest.java | 6 +- .../codeinspector/CfInstructionSubject.java | 22 +- .../codeinspector/DexInstructionSubject.java | 54 ++- .../codeinspector/InstructionSubject.java | 8 +- 7 files changed, 1210 insertions(+), 69 deletions(-) create mode 100644 src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java create mode 100644 src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java index 93ad3ff0af..f642a95a0b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java @@ -4,9 +4,11 @@ package com.android.tools.r8.ir.conversion.passes; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Add; import com.android.tools.r8.ir.code.And; import com.android.tools.r8.ir.code.Binop; @@ -42,18 +44,17 @@ public BinopRewriter(AppView appView) { private Map, BinopDescriptor> createBinopDescriptors() { ImmutableMap.Builder, BinopDescriptor> builder = ImmutableMap.builder(); - builder.put(Add.class, new BinopDescriptor(0, 0, null, null)); - builder.put(Sub.class, new BinopDescriptor(null, 0, null, null)); - builder.put(Mul.class, new BinopDescriptor(1, 1, 0, 0)); - // The following two can be improved if we handle ZeroDivide. - builder.put(Div.class, new BinopDescriptor(null, 1, null, null)); - builder.put(Rem.class, new BinopDescriptor(null, null, null, null)); - builder.put(And.class, new BinopDescriptor(ALL_BITS_SET, ALL_BITS_SET, 0, 0)); - builder.put(Or.class, new BinopDescriptor(0, 0, ALL_BITS_SET, ALL_BITS_SET)); - builder.put(Xor.class, new BinopDescriptor(0, 0, null, null)); - builder.put(Shl.class, new BinopDescriptor(null, 0, 0, null)); - builder.put(Shr.class, new BinopDescriptor(null, 0, 0, null)); - builder.put(Ushr.class, new BinopDescriptor(null, 0, 0, null)); + builder.put(Add.class, BinopDescriptor.ADD); + builder.put(Sub.class, BinopDescriptor.SUB); + builder.put(Mul.class, BinopDescriptor.MUL); + builder.put(Div.class, BinopDescriptor.DIV); + builder.put(Rem.class, BinopDescriptor.REM); + builder.put(And.class, BinopDescriptor.AND); + builder.put(Or.class, BinopDescriptor.OR); + builder.put(Xor.class, BinopDescriptor.XOR); + builder.put(Shl.class, BinopDescriptor.SHL); + builder.put(Shr.class, BinopDescriptor.SHR); + builder.put(Ushr.class, BinopDescriptor.USHR); return builder.build(); } @@ -64,24 +65,176 @@ private Map, BinopDescriptor> createBinopDescriptors() { * - i is right identity if for each x in K, x * i = x. * - a is left absorbing if for each x in K, a * x = a. * - a is right absorbing if for each x in K, x * a = a. + * In a space K, a binop * is associative if for each x,y,z in K, (x * y) * z = x * (y * z). * */ - private static class BinopDescriptor { + private enum BinopDescriptor { + ADD(0, 0, null, null, true) { + @Override + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + return Add.create(numericType, dest, left, right); + } + + @Override + int evaluate(int left, int right) { + return left + right; + } + + @Override + long evaluate(long left, long right) { + return left + right; + } + }, + SUB(null, 0, null, null, false) { + @Override + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + return new Sub(numericType, dest, left, right); + } + + @Override + int evaluate(int left, int right) { + return left - right; + } + + @Override + long evaluate(long left, long right) { + return left - right; + } + }, + MUL(1, 1, 0, 0, true) { + @Override + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + return Mul.create(numericType, dest, left, right); + } + + @Override + int evaluate(int left, int right) { + return left * right; + } + + @Override + long evaluate(long left, long right) { + return left * right; + } + }, + // The following two can be improved if we handle ZeroDivide. + DIV(null, 1, null, null, false), + REM(null, null, null, null, false), + AND(ALL_BITS_SET, ALL_BITS_SET, 0, 0, true) { + @Override + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + return And.create(numericType, dest, left, right); + } + + @Override + int evaluate(int left, int right) { + return left & right; + } + + @Override + long evaluate(long left, long right) { + return left & right; + } + }, + OR(0, 0, ALL_BITS_SET, ALL_BITS_SET, true) { + @Override + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + return Or.create(numericType, dest, left, right); + } + + @Override + int evaluate(int left, int right) { + return left | right; + } + + @Override + long evaluate(long left, long right) { + return left | right; + } + }, + XOR(0, 0, null, null, true) { + @Override + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + return Xor.create(numericType, dest, left, right); + } + + @Override + int evaluate(int left, int right) { + return left ^ right; + } + + @Override + long evaluate(long left, long right) { + return left ^ right; + } + }, + SHL(null, 0, 0, null, false) { + @Override + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + return new Shl(numericType, dest, left, right); + } + + @Override + boolean isShift() { + return true; + } + }, + SHR(null, 0, 0, null, false) { + @Override + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + return new Shr(numericType, dest, left, right); + } + + @Override + boolean isShift() { + return true; + } + }, + USHR(null, 0, 0, null, false) { + @Override + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + return new Ushr(numericType, dest, left, right); + } + + @Override + boolean isShift() { + return true; + } + }; final Integer leftIdentity; final Integer rightIdentity; final Integer leftAbsorbing; final Integer rightAbsorbing; + final boolean associativeAndCommutative; - private BinopDescriptor( + BinopDescriptor( Integer leftIdentity, Integer rightIdentity, Integer leftAbsorbing, - Integer rightAbsorbing) { + Integer rightAbsorbing, + boolean associativeAndCommutative) { this.leftIdentity = leftIdentity; this.rightIdentity = rightIdentity; this.leftAbsorbing = leftAbsorbing; this.rightAbsorbing = rightAbsorbing; + this.associativeAndCommutative = associativeAndCommutative; + } + + Binop instantiate(NumericType numericType, Value dest, Value left, Value right) { + throw new Unreachable(); + } + + int evaluate(int left, int right) { + throw new Unreachable(); + } + + long evaluate(long left, long right) { + throw new Unreachable(); + } + + boolean isShift() { + return false; } } @@ -106,30 +259,10 @@ public void rewriteCode(ProgramMethod method, IRCode code) { || binop.getNumericType() == NumericType.LONG) { BinopDescriptor binopDescriptor = descriptors.get(binop.getClass()); assert binopDescriptor != null; - ConstNumber constNumber = getConstNumber(binop.leftValue()); - if (constNumber != null) { - if (simplify( - binop, - iterator, - constNumber, - binopDescriptor.leftIdentity, - binop.rightValue(), - binopDescriptor.leftAbsorbing, - binop.leftValue())) { - continue; - } - } - constNumber = getConstNumber(binop.rightValue()); - if (constNumber != null) { - simplify( - binop, - iterator, - constNumber, - binopDescriptor.rightIdentity, - binop.leftValue(), - binopDescriptor.rightAbsorbing, - binop.rightValue()); + if (identityAbsorbingSimplification(iterator, binop, binopDescriptor)) { + continue; } + successiveSimplification(iterator, binop, binopDescriptor, code); } } } @@ -137,6 +270,138 @@ public void rewriteCode(ProgramMethod method, IRCode code) { assert code.isConsistentSSA(appView); } + private void successiveSimplification( + InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor, IRCode code) { + if (binop.outValue().hasDebugUsers()) { + return; + } + ConstNumber constLeft = getConstNumber(binop.leftValue()); + ConstNumber constRight = getConstNumber(binop.rightValue()); + if ((constLeft != null && constRight != null) || (constLeft == null && constRight == null)) { + return; + } + Value otherValue = constLeft == null ? binop.leftValue() : binop.rightValue(); + if (otherValue.isPhi() || !otherValue.getDefinition().isBinop()) { + return; + } + Binop prevBinop = otherValue.getDefinition().asBinop(); + ConstNumber prevConstLeft = getConstNumber(prevBinop.leftValue()); + ConstNumber prevConstRight = getConstNumber(prevBinop.rightValue()); + if ((prevConstLeft != null && prevConstRight != null) + || (prevConstLeft == null && prevConstRight == null)) { + return; + } + ConstNumber constB = constLeft == null ? constRight : constLeft; + ConstNumber constA = prevConstLeft == null ? prevConstRight : prevConstLeft; + Value input = prevConstLeft == null ? prevBinop.leftValue() : prevBinop.rightValue(); + // We have two successive binops so that a,b constants, x the input and a * x * b. + if (prevBinop.getClass() == binop.getClass()) { + if (binopDescriptor.associativeAndCommutative) { + // a * x * b => x * (a * b) where (a * b) is a constant. + assert binop.isCommutative(); + Value newConst = addNewConstNumber(code, iterator, constB, constA, binopDescriptor); + iterator.replaceCurrentInstruction( + instantiateBinop(code, input, newConst, binopDescriptor)); + } else if (binopDescriptor.isShift()) { + // x shift: a shift: b => x shift: (a + b) where a + b is a constant. + if (constRight != null && prevConstRight != null) { + Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD); + iterator.replaceCurrentInstruction( + instantiateBinop(code, input, newConst, binopDescriptor)); + } + } else if (binop.isSub()) { + // a - x - b => (a - b) - x where (a - b) is a constant. + // x - a - b => x - (a + b) where (a + b) is a constant. + if (prevConstRight == null) { + Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB); + iterator.replaceCurrentInstruction( + instantiateBinop(code, newConst, input, BinopDescriptor.SUB)); + } else { + Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD); + iterator.replaceCurrentInstruction( + instantiateBinop(code, input, newConst, BinopDescriptor.SUB)); + } + } + } else { + if (binop.isSub() && prevBinop.isAdd()) { + // x + a - b => x + (a - b) where (a - b) is a constant. + // a + x - b => x + (a - b) where (a - b) is a constant. + Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB); + iterator.replaceCurrentInstruction( + instantiateBinop(code, newConst, input, BinopDescriptor.ADD)); + } else if (binop.isAdd() && prevBinop.isSub()) { + // x - a + b => x - (a - b) where (a - b) is a constant. + // a - x + b => (a + b) - x where (a + b) is a constant. + if (prevConstLeft == null) { + Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB); + iterator.replaceCurrentInstruction( + instantiateBinop(code, input, newConst, BinopDescriptor.SUB)); + } else { + Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD); + iterator.replaceCurrentInstruction( + instantiateBinop(code, newConst, input, BinopDescriptor.SUB)); + } + } + } + } + + private Instruction instantiateBinop( + IRCode code, Value left, Value right, BinopDescriptor descriptor) { + TypeElement representative = left.getType().isInt() ? right.getType() : left.getType(); + Value newValue = code.createValue(representative); + NumericType numericType = representative.isInt() ? NumericType.INT : NumericType.LONG; + return descriptor.instantiate(numericType, newValue, left, right); + } + + private Value addNewConstNumber( + IRCode code, + InstructionListIterator iterator, + ConstNumber left, + ConstNumber right, + BinopDescriptor descriptor) { + TypeElement representative = + left.outValue().getType().isInt() ? right.outValue().getType() : left.outValue().getType(); + long result = + representative.isInt() + ? descriptor.evaluate(left.getIntValue(), right.getIntValue()) + : descriptor.evaluate(left.getLongValue(), right.getLongValue()); + iterator.previous(); + Value value = + iterator.insertConstNumberInstruction( + code, appView.options(), result, left.outValue().getType()); + iterator.next(); + return value; + } + + private boolean identityAbsorbingSimplification( + InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor) { + ConstNumber constNumber = getConstNumber(binop.leftValue()); + if (constNumber != null) { + if (simplify( + binop, + iterator, + constNumber, + binopDescriptor.leftIdentity, + binop.rightValue(), + binopDescriptor.leftAbsorbing, + binop.leftValue())) { + return true; + } + } + constNumber = getConstNumber(binop.rightValue()); + if (constNumber != null) { + return simplify( + binop, + iterator, + constNumber, + binopDescriptor.rightIdentity, + binop.leftValue(), + binopDescriptor.rightAbsorbing, + binop.rightValue()); + } + return false; + } + private ConstNumber getConstNumber(Value val) { ConstNumber constNumber = getConstNumberIfConstant(val); if (constNumber != null) { diff --git a/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java b/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java new file mode 100644 index 0000000000..8a6f9c0c63 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java @@ -0,0 +1,424 @@ +// Copyright (c) 2023, 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.ir; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class AssociativeIntTest extends TestBase { + + private static final String EXPECTED_RESULT = + StringUtils.lines( + "Associative", + "7", + "47", + "-2147483644", + "-2147483643", + "12", + "252", + "-6", + "0", + "2", + "2", + "2", + "0", + "3", + "43", + "2147483647", + "-2147483645", + "3", + "43", + "2147483646", + "-2147483647", + "Shift", + "64", + "1344", + "-32", + "0", + "0", + "1", + "67108863", + "-67108864", + "0", + "1", + "67108863", + "67108864", + "Sub", + "-1", + "-41", + "-2147483646", + "-2147483647", + "-3", + "37", + "2147483642", + "2147483643", + "Mixed", + "3", + "43", + "-2147483648", + "-2147483647", + "3", + "-37", + "-2147483642", + "-2147483643", + "3", + "43", + "-2147483648", + "-2147483647", + "1", + "41", + "2147483646", + "2147483647", + "Double Associative", + "12", + "52", + "84", + "1764", + "2", + "2", + "7", + "47", + "4", + "44", + "Double Shift", + "128", + "2688", + "0", + "0", + "0", + "0", + "Double Sub", + "-1", + "-41", + "-10", + "30", + "Double Mixed", + "-4", + "36", + "7", + "-33", + "-4", + "36", + "5", + "45"); + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build(); + } + + public AssociativeIntTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testD8() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_RESULT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .setMinApi(parameters) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_RESULT); + } + + private void inspect(CodeInspector inspector) { + ClassSubject clazz = inspector.clazz(Main.class); + for (FoundMethodSubject method : + clazz.allMethods(m -> m.getParameters().size() > 0 && m.getParameter(0).is("int"))) { + assertEquals( + 1, + method + .streamInstructions() + .filter(i -> i.isIntArithmeticBinop() || i.isIntLogicalBinop()) + .count()); + } + } + + public static class Main { + + public static void main(String[] args) { + simple(); + doubleOps(); + } + + @NeverInline + private static void simple() { + // Associative + * & | ^. + System.out.println("Associative"); + add(2); + add(42); + add(Integer.MAX_VALUE); + add(Integer.MIN_VALUE); + mul(2); + mul(42); + mul(Integer.MAX_VALUE); + mul(Integer.MIN_VALUE); + and(2); + and(42); + and(Integer.MAX_VALUE); + and(Integer.MIN_VALUE); + or(2); + or(42); + or(Integer.MAX_VALUE); + or(Integer.MIN_VALUE); + xor(2); + xor(42); + xor(Integer.MAX_VALUE); + xor(Integer.MIN_VALUE); + + // Shift composition. + System.out.println("Shift"); + shl(2); + shl(42); + shl(Integer.MAX_VALUE); + shl(Integer.MIN_VALUE); + shr(2); + shr(42); + shr(Integer.MAX_VALUE); + shr(Integer.MIN_VALUE); + ushr(2); + ushr(42); + ushr(Integer.MAX_VALUE); + ushr(Integer.MIN_VALUE); + + // Special for -. + System.out.println("Sub"); + sub(2); + sub(42); + sub(Integer.MAX_VALUE); + sub(Integer.MIN_VALUE); + sub2(2); + sub2(42); + sub2(Integer.MAX_VALUE); + sub2(Integer.MIN_VALUE); + + // Mixed for + and -. + System.out.println("Mixed"); + addSub(2); + addSub(42); + addSub(Integer.MAX_VALUE); + addSub(Integer.MIN_VALUE); + subAdd(2); + subAdd(42); + subAdd(Integer.MAX_VALUE); + subAdd(Integer.MIN_VALUE); + addSub2(2); + addSub2(42); + addSub2(Integer.MAX_VALUE); + addSub2(Integer.MIN_VALUE); + subAdd2(2); + subAdd2(42); + subAdd2(Integer.MAX_VALUE); + subAdd2(Integer.MIN_VALUE); + } + + @NeverInline + private static void doubleOps() { + // Associative + * & | ^. + System.out.println("Double Associative"); + addDouble(2); + addDouble(42); + mulDouble(2); + mulDouble(42); + andDouble(2); + andDouble(42); + orDouble(2); + orDouble(42); + xorDouble(2); + xorDouble(42); + + // Shift composition. + System.out.println("Double Shift"); + shlDouble(2); + shlDouble(42); + shrDouble(2); + shrDouble(42); + ushrDouble(2); + ushrDouble(42); + + // Special for -. + System.out.println("Double Sub"); + subDouble(2); + subDouble(42); + sub2Double(2); + sub2Double(42); + + // Mixed for + and -. + System.out.println("Double Mixed"); + addSubDouble(2); + addSubDouble(42); + subAddDouble(2); + subAddDouble(42); + addSub2Double(2); + addSub2Double(42); + subAdd2Double(2); + subAdd2Double(42); + } + + @NeverInline + public static void add(int x) { + System.out.println(3 + x + 2); + } + + @NeverInline + public static void mul(int x) { + System.out.println(3 * x * 2); + } + + @NeverInline + public static void and(int x) { + System.out.println(3 & x & 2); + } + + @NeverInline + public static void or(int x) { + System.out.println(3 | x | 2); + } + + @NeverInline + public static void xor(int x) { + System.out.println(3 ^ x ^ 2); + } + + @NeverInline + public static void shl(int x) { + System.out.println(x << 2 << 3); + } + + @NeverInline + public static void shr(int x) { + System.out.println(x >> 2 >> 3); + } + + @NeverInline + public static void ushr(int x) { + System.out.println(x >>> 2 >>> 3); + } + + @NeverInline + public static void sub(int x) { + System.out.println(3 - x - 2); + } + + @NeverInline + public static void sub2(int x) { + System.out.println(x - 3 - 2); + } + + @NeverInline + public static void addSub(int x) { + System.out.println(3 + x - 2); + } + + @NeverInline + public static void addSub2(int x) { + System.out.println(x + 3 - 2); + } + + @NeverInline + public static void subAdd(int x) { + System.out.println(3 - x + 2); + } + + @NeverInline + public static void subAdd2(int x) { + System.out.println(x - 3 + 2); + } + + @NeverInline + public static void addDouble(int x) { + System.out.println(3 + x + 2 + 5); + } + + @NeverInline + public static void mulDouble(int x) { + System.out.println(3 * x * 2 * 7); + } + + @NeverInline + public static void andDouble(int x) { + System.out.println(3 & x & 2 & 7); + } + + @NeverInline + public static void orDouble(int x) { + System.out.println(3 | x | 2 | 7); + } + + @NeverInline + public static void xorDouble(int x) { + System.out.println(3 ^ x ^ 2 ^ 7); + } + + @NeverInline + public static void shlDouble(int x) { + System.out.println(x << 2 << 3 << 1); + } + + @NeverInline + public static void shrDouble(int x) { + System.out.println(x >> 2 >> 3 >> 1); + } + + @NeverInline + public static void ushrDouble(int x) { + System.out.println(x >>> 2 >>> 3 >>> 1); + } + + @NeverInline + public static void subDouble(int x) { + System.out.println(3 - x - 2); + } + + @NeverInline + public static void sub2Double(int x) { + System.out.println(x - 3 - 2 - 7); + } + + @NeverInline + public static void addSubDouble(int x) { + System.out.println(3 + x - 2 - 7); + } + + @NeverInline + public static void addSub2Double(int x) { + System.out.println(x + 3 - 2 - 7); + } + + @NeverInline + public static void subAddDouble(int x) { + System.out.println(3 - x + 2 + 4); + } + + @NeverInline + public static void subAdd2Double(int x) { + System.out.println(x - 3 + 2 + 4); + } + } +} diff --git a/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java b/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java new file mode 100644 index 0000000000..22ea7e8ca9 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java @@ -0,0 +1,424 @@ +// Copyright (c) 2023, 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.ir; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class AssociativeLongTest extends TestBase { + + private static final String EXPECTED_RESULT = + StringUtils.lines( + "Associative", + "7", + "47", + "-9223372036854775804", + "-9223372036854775803", + "12", + "252", + "-6", + "0", + "2", + "2", + "2", + "0", + "3", + "43", + "9223372036854775807", + "-9223372036854775805", + "3", + "43", + "9223372036854775806", + "-9223372036854775807", + "Shift", + "64", + "1344", + "-32", + "0", + "0", + "1", + "288230376151711743", + "-288230376151711744", + "0", + "1", + "288230376151711743", + "288230376151711744", + "Sub", + "-1", + "-41", + "-9223372036854775806", + "-9223372036854775807", + "-3", + "37", + "9223372036854775802", + "9223372036854775803", + "Mixed", + "3", + "43", + "-9223372036854775808", + "-9223372036854775807", + "3", + "-37", + "-9223372036854775802", + "-9223372036854775803", + "3", + "43", + "-9223372036854775808", + "-9223372036854775807", + "1", + "41", + "9223372036854775806", + "9223372036854775807", + "Double Associative", + "12", + "52", + "84", + "1764", + "2", + "2", + "7", + "47", + "4", + "44", + "Double Shift", + "128", + "2688", + "0", + "0", + "0", + "0", + "Double Sub", + "-1", + "-41", + "-10", + "30", + "Double Mixed", + "-4", + "36", + "7", + "-33", + "-4", + "36", + "5", + "45"); + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build(); + } + + public AssociativeLongTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testD8() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_RESULT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .setMinApi(parameters) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_RESULT); + } + + private void inspect(CodeInspector inspector) { + ClassSubject clazz = inspector.clazz(Main.class); + for (FoundMethodSubject method : + clazz.allMethods(m -> m.getParameters().size() > 0 && m.getParameter(0).is("long"))) { + assertEquals( + 1, + method + .streamInstructions() + .filter(i -> i.isIntArithmeticBinop() || i.isLongLogicalBinop()) + .count()); + } + } + + public static class Main { + + public static void main(String[] args) { + simple(); + doubleOps(); + } + + @NeverInline + private static void simple() { + // Associative + * & | ^. + System.out.println("Associative"); + add(2); + add(42); + add(Long.MAX_VALUE); + add(Long.MIN_VALUE); + mul(2); + mul(42); + mul(Long.MAX_VALUE); + mul(Long.MIN_VALUE); + and(2); + and(42); + and(Long.MAX_VALUE); + and(Long.MIN_VALUE); + or(2); + or(42); + or(Long.MAX_VALUE); + or(Long.MIN_VALUE); + xor(2); + xor(42); + xor(Long.MAX_VALUE); + xor(Long.MIN_VALUE); + + // Shift composition. + System.out.println("Shift"); + shl(2); + shl(42); + shl(Long.MAX_VALUE); + shl(Long.MIN_VALUE); + shr(2); + shr(42); + shr(Long.MAX_VALUE); + shr(Long.MIN_VALUE); + ushr(2); + ushr(42); + ushr(Long.MAX_VALUE); + ushr(Long.MIN_VALUE); + + // Special for -. + System.out.println("Sub"); + sub(2); + sub(42); + sub(Long.MAX_VALUE); + sub(Long.MIN_VALUE); + sub2(2); + sub2(42); + sub2(Long.MAX_VALUE); + sub2(Long.MIN_VALUE); + + // Mixed for + and -. + System.out.println("Mixed"); + addSub(2); + addSub(42); + addSub(Long.MAX_VALUE); + addSub(Long.MIN_VALUE); + subAdd(2); + subAdd(42); + subAdd(Long.MAX_VALUE); + subAdd(Long.MIN_VALUE); + addSub2(2); + addSub2(42); + addSub2(Long.MAX_VALUE); + addSub2(Long.MIN_VALUE); + subAdd2(2); + subAdd2(42); + subAdd2(Long.MAX_VALUE); + subAdd2(Long.MIN_VALUE); + } + + @NeverInline + private static void doubleOps() { + // Associative + * & | ^. + System.out.println("Double Associative"); + addDouble(2); + addDouble(42); + mulDouble(2); + mulDouble(42); + andDouble(2); + andDouble(42); + orDouble(2); + orDouble(42); + xorDouble(2); + xorDouble(42); + + // Shift composition. + System.out.println("Double Shift"); + shlDouble(2); + shlDouble(42); + shrDouble(2); + shrDouble(42); + ushrDouble(2); + ushrDouble(42); + + // Special for -. + System.out.println("Double Sub"); + subDouble(2); + subDouble(42); + sub2Double(2); + sub2Double(42); + + // Mixed for + and -. + System.out.println("Double Mixed"); + addSubDouble(2); + addSubDouble(42); + subAddDouble(2); + subAddDouble(42); + addSub2Double(2); + addSub2Double(42); + subAdd2Double(2); + subAdd2Double(42); + } + + @NeverInline + public static void add(long x) { + System.out.println(3L + x + 2L); + } + + @NeverInline + public static void mul(long x) { + System.out.println(3L * x * 2L); + } + + @NeverInline + public static void and(long x) { + System.out.println(3L & x & 2L); + } + + @NeverInline + public static void or(long x) { + System.out.println(3L | x | 2L); + } + + @NeverInline + public static void xor(long x) { + System.out.println(3L ^ x ^ 2L); + } + + @NeverInline + public static void shl(long x) { + System.out.println(x << 2L << 3L); + } + + @NeverInline + public static void shr(long x) { + System.out.println(x >> 2L >> 3L); + } + + @NeverInline + public static void ushr(long x) { + System.out.println(x >>> 2L >>> 3L); + } + + @NeverInline + public static void sub(long x) { + System.out.println(3L - x - 2L); + } + + @NeverInline + public static void sub2(long x) { + System.out.println(x - 3L - 2L); + } + + @NeverInline + public static void addSub(long x) { + System.out.println(3L + x - 2L); + } + + @NeverInline + public static void addSub2(long x) { + System.out.println(x + 3L - 2L); + } + + @NeverInline + public static void subAdd(long x) { + System.out.println(3L - x + 2L); + } + + @NeverInline + public static void subAdd2(long x) { + System.out.println(x - 3L + 2L); + } + + @NeverInline + public static void addDouble(long x) { + System.out.println(3L + x + 2L + 5); + } + + @NeverInline + public static void mulDouble(long x) { + System.out.println(3L * x * 2L * 7L); + } + + @NeverInline + public static void andDouble(long x) { + System.out.println(3L & x & 2L & 7L); + } + + @NeverInline + public static void orDouble(long x) { + System.out.println(3L | x | 2L | 7L); + } + + @NeverInline + public static void xorDouble(long x) { + System.out.println(3L ^ x ^ 2L ^ 7L); + } + + @NeverInline + public static void shlDouble(long x) { + System.out.println(x << 2L << 3L << 1L); + } + + @NeverInline + public static void shrDouble(long x) { + System.out.println(x >> 2L >> 3L >> 1L); + } + + @NeverInline + public static void ushrDouble(long x) { + System.out.println(x >>> 2L >>> 3L >>> 1L); + } + + @NeverInline + public static void subDouble(long x) { + System.out.println(3L - x - 2L); + } + + @NeverInline + public static void sub2Double(long x) { + System.out.println(x - 3L - 2L - 7L); + } + + @NeverInline + public static void addSubDouble(long x) { + System.out.println(3L + x - 2L - 7L); + } + + @NeverInline + public static void addSub2Double(long x) { + System.out.println(x + 3L - 2L - 7L); + } + + @NeverInline + public static void subAddDouble(long x) { + System.out.println(3L - x + 2L + 4L); + } + + @NeverInline + public static void subAdd2Double(long x) { + System.out.println(x - 3L + 2L + 4L); + } + } +} diff --git a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java index 390d6ca407..3f9a8b27fc 100644 --- a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java +++ b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java @@ -853,7 +853,11 @@ private void inspect(CodeInspector inspector) { assertTrue( m.streamInstructions() .noneMatch( - i -> i.isIntOrLongLogicalBinop() || i.isIntOrLongArithmeticBinop()))); + i -> + i.isIntLogicalBinop() + || i.isLongLogicalBinop() + || i.isIntArithmeticBinop() + || i.isLongArithmeticBinop()))); } static class Main { diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java index 45d62025b4..79425a3740 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java @@ -339,17 +339,27 @@ public boolean isStore() { } @Override - public boolean isIntOrLongArithmeticBinop() { + public boolean isIntArithmeticBinop() { return instruction instanceof CfArithmeticBinop - && (((CfArithmeticBinop) instruction).getType() == NumericType.INT - || ((CfArithmeticBinop) instruction).getType() == NumericType.LONG); + && ((CfArithmeticBinop) instruction).getType() == NumericType.INT; } @Override - public boolean isIntOrLongLogicalBinop() { + public boolean isIntLogicalBinop() { return instruction instanceof CfLogicalBinop - && (((CfLogicalBinop) instruction).getType() == NumericType.INT - || ((CfLogicalBinop) instruction).getType() == NumericType.LONG); + && ((CfLogicalBinop) instruction).getType() == NumericType.INT; + } + + @Override + public boolean isLongArithmeticBinop() { + return instruction instanceof CfArithmeticBinop + && ((CfArithmeticBinop) instruction).getType() == NumericType.LONG; + } + + @Override + public boolean isLongLogicalBinop() { + return instruction instanceof CfLogicalBinop + && ((CfLogicalBinop) instruction).getType() == NumericType.LONG; } @Override diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java index 38421a284a..bd6842228f 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java @@ -122,6 +122,8 @@ import com.android.tools.r8.dex.code.DexReturn; import com.android.tools.r8.dex.code.DexReturnObject; import com.android.tools.r8.dex.code.DexReturnVoid; +import com.android.tools.r8.dex.code.DexRsubInt; +import com.android.tools.r8.dex.code.DexRsubIntLit8; import com.android.tools.r8.dex.code.DexSget; import com.android.tools.r8.dex.code.DexSgetBoolean; import com.android.tools.r8.dex.code.DexSgetByte; @@ -501,69 +503,77 @@ public boolean isSparseSwitch() { return instruction instanceof DexSparseSwitch; } - public boolean isIntOrLongArithmeticBinop() { + public boolean isIntArithmeticBinop() { return instruction instanceof DexMulInt || instruction instanceof DexMulIntLit8 || instruction instanceof DexMulIntLit16 || instruction instanceof DexMulInt2Addr - || instruction instanceof DexMulLong - || instruction instanceof DexMulLong2Addr || instruction instanceof DexAddInt || instruction instanceof DexAddIntLit8 || instruction instanceof DexAddIntLit16 || instruction instanceof DexAddInt2Addr - || instruction instanceof DexAddLong - || instruction instanceof DexAddLong2Addr + || instruction instanceof DexRsubInt + || instruction instanceof DexRsubIntLit8 || instruction instanceof DexSubInt || instruction instanceof DexSubInt2Addr - || instruction instanceof DexSubLong - || instruction instanceof DexSubLong2Addr || instruction instanceof DexDivInt || instruction instanceof DexDivIntLit8 || instruction instanceof DexDivIntLit16 || instruction instanceof DexDivInt2Addr - || instruction instanceof DexDivLong - || instruction instanceof DexDivLong2Addr || instruction instanceof DexRemInt || instruction instanceof DexRemIntLit8 || instruction instanceof DexRemIntLit16 - || instruction instanceof DexRemInt2Addr + || instruction instanceof DexRemInt2Addr; + } + + public boolean isLongArithmeticBinop() { + return instruction instanceof DexMulLong + || instruction instanceof DexMulLong2Addr + || instruction instanceof DexAddLong + || instruction instanceof DexAddLong2Addr + || instruction instanceof DexSubLong + || instruction instanceof DexSubLong2Addr + || instruction instanceof DexDivLong + || instruction instanceof DexDivLong2Addr || instruction instanceof DexRemLong || instruction instanceof DexRemLong2Addr; } - public boolean isIntOrLongLogicalBinop() { + public boolean isIntLogicalBinop() { return instruction instanceof DexAndInt || instruction instanceof DexAndIntLit8 || instruction instanceof DexAndIntLit16 || instruction instanceof DexAndInt2Addr - || instruction instanceof DexAndLong - || instruction instanceof DexAndLong2Addr || instruction instanceof DexOrInt || instruction instanceof DexOrIntLit8 || instruction instanceof DexOrIntLit16 || instruction instanceof DexOrInt2Addr - || instruction instanceof DexOrLong - || instruction instanceof DexOrLong2Addr || instruction instanceof DexXorInt || instruction instanceof DexXorIntLit8 || instruction instanceof DexXorIntLit16 || instruction instanceof DexXorInt2Addr - || instruction instanceof DexXorLong - || instruction instanceof DexXorLong2Addr || instruction instanceof DexShrInt || instruction instanceof DexShrIntLit8 || instruction instanceof DexShrInt2Addr - || instruction instanceof DexShrLong - || instruction instanceof DexShrLong2Addr || instruction instanceof DexShlInt || instruction instanceof DexShlIntLit8 || instruction instanceof DexShlInt2Addr - || instruction instanceof DexShlLong - || instruction instanceof DexShlLong2Addr || instruction instanceof DexUshrInt || instruction instanceof DexUshrIntLit8 - || instruction instanceof DexUshrInt2Addr + || instruction instanceof DexUshrInt2Addr; + } + + public boolean isLongLogicalBinop() { + return instruction instanceof DexAndLong + || instruction instanceof DexAndLong2Addr + || instruction instanceof DexOrLong + || instruction instanceof DexOrLong2Addr + || instruction instanceof DexXorLong + || instruction instanceof DexXorLong2Addr + || instruction instanceof DexShrLong + || instruction instanceof DexShrLong2Addr + || instruction instanceof DexShlLong + || instruction instanceof DexShlLong2Addr || instruction instanceof DexUshrLong || instruction instanceof DexUshrLong2Addr; } diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java index 6d1bd94e3d..b77029f146 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java @@ -140,9 +140,13 @@ default SwitchInstructionSubject asSwitch() { boolean isSparseSwitch(); - boolean isIntOrLongArithmeticBinop(); + boolean isIntArithmeticBinop(); - boolean isIntOrLongLogicalBinop(); + boolean isIntLogicalBinop(); + + boolean isLongArithmeticBinop(); + + boolean isLongLogicalBinop(); boolean isMultiplication(); From 6fbe2a630b398b781e3f3db7254f69b9ee07f6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gjesse?= Date: Wed, 7 Jun 2023 09:49:36 +0200 Subject: [PATCH 020/153] Clear all collections when aborting a transaction Missing clearing the method handles collection could cause the method the method handle is referring to to not get added when a method handle was added again in the next transaction. Bug: b/280644447 Change-Id: I958ca279f9db614faab0813e7c8cb16a57f3e27c --- .../java/com/android/tools/r8/dex/VirtualFile.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java index 875a4feb5c..7e8904da34 100644 --- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java +++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java @@ -1124,15 +1124,22 @@ void abort() { protos.clear(); types.clear(); strings.clear(); + callSites.clear(); + methodHandles.clear(); indexedItemsReferencedFromClassesInTransaction.clear(); } public boolean isEmpty() { - return classes.isEmpty() && fields.isEmpty() && methods.isEmpty() && protos.isEmpty() - && types.isEmpty() && strings.isEmpty(); + return classes.isEmpty() + && fields.isEmpty() + && methods.isEmpty() + && protos.isEmpty() + && types.isEmpty() + && strings.isEmpty() + && callSites.isEmpty() + && methodHandles.isEmpty(); } - } /** From 9a84f5a746dab69d455dddcc499b06541591e159 Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Wed, 7 Jun 2023 10:48:32 +0200 Subject: [PATCH 021/153] Fix tests that don't ensure unique SSA value numbers Change-Id: I8cf1560e8865407bcd160b25458c156917b1d20c --- .../com/android/tools/r8/ir/code/IRCode.java | 7 +++++-- .../android/tools/r8/utils/InternalOptions.java | 1 + .../java/com/android/tools/r8/ir/InlineTest.java | 4 ++++ .../android/tools/r8/ir/IrInjectionTestBase.java | 16 ++++++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index edf02466e4..d72358a2e3 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java @@ -694,11 +694,14 @@ public boolean hasCatchHandlers() { return false; } - private static void addValueAndCheckUniqueNumber(Int2ReferenceMap values, Value value) { + private void addValueAndCheckUniqueNumber(Int2ReferenceMap values, Value value) { assert value != null; int number = value.getNumber(); Value old = values.put(number, value); - assert old == null || old == value || (number == -1 && value.isValueOnStack()) + assert options.testing.ignoreValueNumbering + || old == null + || old == value + || (number == -1 && value.isValueOnStack()) : "Multiple value definitions with number " + number + ": " + value + " and " + old; } 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 89edec3862..538b9ee49c 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -2078,6 +2078,7 @@ public static class TestingOptions { public boolean roundtripThroughLir = false; public boolean checkReceiverAlwaysNullInCallSiteOptimization = true; public boolean forceInlineAPIConversions = false; + public boolean ignoreValueNumbering = false; private boolean hasReadCheckDeterminism = false; private DeterminismChecker determinismChecker = null; public boolean usePcEncodingInCfForTesting = false; diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java index d89807ce60..9100c82a65 100644 --- a/src/test/java/com/android/tools/r8/ir/InlineTest.java +++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java @@ -72,6 +72,10 @@ private TestApplication buildTestApplication( MethodSubject method, List additionalCode) throws ExecutionException { + // Some tests play fast and loose with IR and the SSA value numbers are not generally unique. + if (additionalCode != null && !additionalCode.isEmpty()) { + options.testing.ignoreValueNumbering = true; + } AppView appView = AppView.createForR8(application.asDirect()); appView.setAppServices(AppServices.builder(appView).build()); ProfileCollectionAdditions profileCollectionAdditions = ProfileCollectionAdditions.nop(); diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java index 0d23c77bf8..25fc777b5b 100644 --- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java +++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java @@ -9,9 +9,11 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.NumberGenerator; +import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; import com.android.tools.r8.smali.SmaliTestBase; @@ -69,6 +71,20 @@ public TestApplication(AppView appView, MethodSubject method, List ad this.code = method.buildIR(); this.additionalCode = additionalCode; this.consumers = new AndroidAppConsumers(appView.options()); + int largestValueNumber = -1; + for (BasicBlock block : code.blocks) { + for (Phi phi : block.getPhis()) { + largestValueNumber = Math.max(largestValueNumber, phi.getNumber()); + } + for (Instruction instruction : block.getInstructions()) { + if (instruction.hasOutValue()) { + largestValueNumber = Math.max(largestValueNumber, instruction.outValue().getNumber()); + } + } + } + while (valueNumberGenerator.peek() <= largestValueNumber) { + valueNumberGenerator.next(); + } } public int countArgumentInstructions() { From c66da095518f3068b70930eac6d1ad92170aebac Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Tue, 6 Jun 2023 13:27:05 +0200 Subject: [PATCH 022/153] Don't assume marked synthetics derive directly from Object Bug: b/286001996 Change-Id: Ic0ae6b569a7cc2366f98ecb093d4751285558260 --- .../tools/r8/synthesis/SyntheticMarker.java | 4 +--- .../backports/ThreadLocalBackportTest.java | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java index cf548f977a..9e8ec2682b 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java @@ -112,6 +112,7 @@ public static void addMarkerToClass( DexProgramClass clazz, SyntheticKind kind, AppView appView) { // TODO(b/158159959): Consider moving this to the dex writer similar to the CF case. assert !appView.options().isGeneratingClassFiles(); + assert !isDefinitelyNotSyntheticProgramClass(clazz); clazz.setAnnotations( clazz .annotations() @@ -148,9 +149,6 @@ public static SyntheticMarker stripMarkerFromClass(DexProgramClass clazz, AppVie private static SyntheticMarker internalStripMarkerFromClass( DexProgramClass clazz, AppView appView) { - if (clazz.superType != appView.dexItemFactory().objectType) { - return NO_MARKER; - } if (isDefinitelyNotSyntheticProgramClass(clazz)) { return NO_MARKER; } diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java index 3c6e599947..8fbadfa389 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java @@ -8,6 +8,8 @@ import static com.android.tools.r8.utils.AndroidApiLevel.LATEST; import static org.hamcrest.CoreMatchers.containsString; +import com.android.tools.r8.D8TestCompileResult; +import com.android.tools.r8.OutputMode; import com.android.tools.r8.TestDiagnosticMessages; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -55,6 +57,24 @@ public void testD8() throws Exception { .apply(this::checkExpected); } + @Test + public void testIntermediateD8() throws Exception { + D8TestCompileResult intermediate = + testForD8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setOutputMode( + parameters.isCfRuntime() ? OutputMode.ClassFile : OutputMode.DexFilePerClass) + .setMinApi(parameters) + .compileWithExpectedDiagnostics(this::checkDiagnostics); + + testForD8(parameters.getBackend()) + .addProgramFiles(intermediate.writeToZip()) + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), TestClass.class) + .apply(this::checkExpected); + } + @Test public void testR8() throws Exception { parameters.assumeR8TestParameters(); From 9fbf4ae8a3038b1029f32762497af3b71c13e874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Wed, 7 Jun 2023 13:23:25 +0200 Subject: [PATCH 023/153] Fix Binop rewriter - TBR red bots - Issues with sub with left constant Change-Id: Ieb7cbb0db0c5857c9adffbdb229b73fdee1569de --- .../ir/conversion/passes/BinopRewriter.java | 35 ++++++++-------- .../tools/r8/ir/AssociativeIntTest.java | 41 ++++++++++++++++++- .../tools/r8/ir/AssociativeLongTest.java | 2 +- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java index f642a95a0b..b95f09db38 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java @@ -275,25 +275,26 @@ private void successiveSimplification( if (binop.outValue().hasDebugUsers()) { return; } - ConstNumber constLeft = getConstNumber(binop.leftValue()); - ConstNumber constRight = getConstNumber(binop.rightValue()); - if ((constLeft != null && constRight != null) || (constLeft == null && constRight == null)) { + ConstNumber constBLeft = getConstNumber(binop.leftValue()); + ConstNumber constBRight = getConstNumber(binop.rightValue()); + if ((constBLeft != null && constBRight != null) + || (constBLeft == null && constBRight == null)) { return; } - Value otherValue = constLeft == null ? binop.leftValue() : binop.rightValue(); + Value otherValue = constBLeft == null ? binop.leftValue() : binop.rightValue(); if (otherValue.isPhi() || !otherValue.getDefinition().isBinop()) { return; } Binop prevBinop = otherValue.getDefinition().asBinop(); - ConstNumber prevConstLeft = getConstNumber(prevBinop.leftValue()); - ConstNumber prevConstRight = getConstNumber(prevBinop.rightValue()); - if ((prevConstLeft != null && prevConstRight != null) - || (prevConstLeft == null && prevConstRight == null)) { + ConstNumber constALeft = getConstNumber(prevBinop.leftValue()); + ConstNumber constARight = getConstNumber(prevBinop.rightValue()); + if ((constALeft != null && constARight != null) + || (constALeft == null && constARight == null)) { return; } - ConstNumber constB = constLeft == null ? constRight : constLeft; - ConstNumber constA = prevConstLeft == null ? prevConstRight : prevConstLeft; - Value input = prevConstLeft == null ? prevBinop.leftValue() : prevBinop.rightValue(); + ConstNumber constB = constBLeft == null ? constBRight : constBLeft; + ConstNumber constA = constALeft == null ? constARight : constALeft; + Value input = constALeft == null ? prevBinop.leftValue() : prevBinop.rightValue(); // We have two successive binops so that a,b constants, x the input and a * x * b. if (prevBinop.getClass() == binop.getClass()) { if (binopDescriptor.associativeAndCommutative) { @@ -304,15 +305,16 @@ private void successiveSimplification( instantiateBinop(code, input, newConst, binopDescriptor)); } else if (binopDescriptor.isShift()) { // x shift: a shift: b => x shift: (a + b) where a + b is a constant. - if (constRight != null && prevConstRight != null) { + if (constBRight != null && constARight != null) { Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD); iterator.replaceCurrentInstruction( instantiateBinop(code, input, newConst, binopDescriptor)); } - } else if (binop.isSub()) { + } else if (binop.isSub() && constBRight != null) { // a - x - b => (a - b) - x where (a - b) is a constant. // x - a - b => x - (a + b) where (a + b) is a constant. - if (prevConstRight == null) { + // We ignore b - (x - a) and b - (a - x) with constBRight != null. + if (constARight == null) { Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB); iterator.replaceCurrentInstruction( instantiateBinop(code, newConst, input, BinopDescriptor.SUB)); @@ -323,16 +325,17 @@ private void successiveSimplification( } } } else { - if (binop.isSub() && prevBinop.isAdd()) { + if (binop.isSub() && prevBinop.isAdd() && constBRight != null) { // x + a - b => x + (a - b) where (a - b) is a constant. // a + x - b => x + (a - b) where (a - b) is a constant. + // We ignore b - (x + a) and b - (a + x) with constBRight != null. Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB); iterator.replaceCurrentInstruction( instantiateBinop(code, newConst, input, BinopDescriptor.ADD)); } else if (binop.isAdd() && prevBinop.isSub()) { // x - a + b => x - (a - b) where (a - b) is a constant. // a - x + b => (a + b) - x where (a + b) is a constant. - if (prevConstLeft == null) { + if (constALeft == null) { Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB); iterator.replaceCurrentInstruction( instantiateBinop(code, input, newConst, BinopDescriptor.SUB)); diff --git a/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java b/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java index 8a6f9c0c63..84e8e703f6 100644 --- a/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java +++ b/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java @@ -75,6 +75,18 @@ public class AssociativeIntTest extends TestBase { "-37", "-2147483642", "-2147483643", + "-1", + "-41", + "-2147483646", + "-2147483647", + "25", + "-15", + "-2147483620", + "-2147483621", + "3", + "43", + "-2147483648", + "-2147483647", "3", "43", "-2147483648", @@ -152,7 +164,7 @@ private void inspect(CodeInspector inspector) { for (FoundMethodSubject method : clazz.allMethods(m -> m.getParameters().size() > 0 && m.getParameter(0).is("int"))) { assertEquals( - 1, + method.getOriginalName().contains("NotSimplified") ? 2 : 1, method .streamInstructions() .filter(i -> i.isIntArithmeticBinop() || i.isIntLogicalBinop()) @@ -228,6 +240,18 @@ private static void simple() { subAdd(42); subAdd(Integer.MAX_VALUE); subAdd(Integer.MIN_VALUE); + addSubNotSimplified_1(2); + addSubNotSimplified_1(42); + addSubNotSimplified_1(Integer.MAX_VALUE); + addSubNotSimplified_1(Integer.MIN_VALUE); + addSubNotSimplified_2(2); + addSubNotSimplified_2(42); + addSubNotSimplified_2(Integer.MAX_VALUE); + addSubNotSimplified_2(Integer.MIN_VALUE); + addSubNotSimplified_3(2); + addSubNotSimplified_3(42); + addSubNotSimplified_3(Integer.MAX_VALUE); + addSubNotSimplified_3(Integer.MIN_VALUE); addSub2(2); addSub2(42); addSub2(Integer.MAX_VALUE); @@ -341,6 +365,21 @@ public static void addSub2(int x) { System.out.println(x + 3 - 2); } + @NeverInline + public static void addSubNotSimplified_1(int x) { + System.out.println(14 - (x + 13)); + } + + @NeverInline + public static void addSubNotSimplified_2(int x) { + System.out.println(14 - (x - 13)); + } + + @NeverInline + public static void addSubNotSimplified_3(int x) { + System.out.println(14 - (13 - x)); + } + @NeverInline public static void subAdd(int x) { System.out.println(3 - x + 2); diff --git a/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java b/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java index 22ea7e8ca9..1d041b8f0d 100644 --- a/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java +++ b/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java @@ -155,7 +155,7 @@ private void inspect(CodeInspector inspector) { 1, method .streamInstructions() - .filter(i -> i.isIntArithmeticBinop() || i.isLongLogicalBinop()) + .filter(i -> i.isLongArithmeticBinop() || i.isLongLogicalBinop()) .count()); } } From ed1d94f032b04f833b5efdeaa7459e977c38c710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Wed, 7 Jun 2023 11:21:12 +0200 Subject: [PATCH 024/153] Extend binop rewriter - support associative followed by identity absorbing. Change-Id: Ib3d8e37da0e7d01ce3dfe34345ea106216053df7 --- .../ir/conversion/passes/BinopRewriter.java | 39 +- .../tools/r8/ir/IdentityAbsorbingIntTest.java | 603 ++++++++++++++++++ ...st.java => IdentityAbsorbingLongTest.java} | 539 +--------------- 3 files changed, 657 insertions(+), 524 deletions(-) create mode 100644 src/test/java/com/android/tools/r8/ir/IdentityAbsorbingIntTest.java rename src/test/java/com/android/tools/r8/ir/{IdentityAbsorbingTest.java => IdentityAbsorbingLongTest.java} (55%) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java index b95f09db38..a0edfc59af 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java @@ -301,14 +301,12 @@ private void successiveSimplification( // a * x * b => x * (a * b) where (a * b) is a constant. assert binop.isCommutative(); Value newConst = addNewConstNumber(code, iterator, constB, constA, binopDescriptor); - iterator.replaceCurrentInstruction( - instantiateBinop(code, input, newConst, binopDescriptor)); + replaceBinop(iterator, code, input, newConst, binopDescriptor); } else if (binopDescriptor.isShift()) { // x shift: a shift: b => x shift: (a + b) where a + b is a constant. if (constBRight != null && constARight != null) { Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD); - iterator.replaceCurrentInstruction( - instantiateBinop(code, input, newConst, binopDescriptor)); + replaceBinop(iterator, code, input, newConst, binopDescriptor); } } else if (binop.isSub() && constBRight != null) { // a - x - b => (a - b) - x where (a - b) is a constant. @@ -316,12 +314,10 @@ private void successiveSimplification( // We ignore b - (x - a) and b - (a - x) with constBRight != null. if (constARight == null) { Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB); - iterator.replaceCurrentInstruction( - instantiateBinop(code, newConst, input, BinopDescriptor.SUB)); + replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB); } else { Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD); - iterator.replaceCurrentInstruction( - instantiateBinop(code, input, newConst, BinopDescriptor.SUB)); + replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB); } } } else { @@ -330,26 +326,37 @@ private void successiveSimplification( // a + x - b => x + (a - b) where (a - b) is a constant. // We ignore b - (x + a) and b - (a + x) with constBRight != null. Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB); - iterator.replaceCurrentInstruction( - instantiateBinop(code, newConst, input, BinopDescriptor.ADD)); + replaceBinop(iterator, code, newConst, input, BinopDescriptor.ADD); } else if (binop.isAdd() && prevBinop.isSub()) { // x - a + b => x - (a - b) where (a - b) is a constant. // a - x + b => (a + b) - x where (a + b) is a constant. if (constALeft == null) { Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB); - iterator.replaceCurrentInstruction( - instantiateBinop(code, input, newConst, BinopDescriptor.SUB)); + replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB); } else { Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD); - iterator.replaceCurrentInstruction( - instantiateBinop(code, newConst, input, BinopDescriptor.SUB)); + replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB); } } } } - private Instruction instantiateBinop( - IRCode code, Value left, Value right, BinopDescriptor descriptor) { + private void replaceBinop( + InstructionListIterator iterator, + IRCode code, + Value left, + Value right, + BinopDescriptor binopDescriptor) { + Binop newBinop = instantiateBinop(code, left, right, binopDescriptor); + iterator.replaceCurrentInstruction(newBinop); + // We need to reset the iterator state after replaceCurrentInstruction so that Iterator#remove() + // can work in identityAbsorbingSimplification by calling previous then next. + iterator.previous(); + iterator.next(); + identityAbsorbingSimplification(iterator, newBinop, binopDescriptor); + } + + private Binop instantiateBinop(IRCode code, Value left, Value right, BinopDescriptor descriptor) { TypeElement representative = left.getType().isInt() ? right.getType() : left.getType(); Value newValue = code.createValue(representative); NumericType numericType = representative.isInt() ? NumericType.INT : NumericType.LONG; diff --git a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingIntTest.java b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingIntTest.java new file mode 100644 index 0000000000..9d7b58dc29 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingIntTest.java @@ -0,0 +1,603 @@ +// Copyright (c) 2023, 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.ir; + +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class IdentityAbsorbingIntTest extends TestBase { + + private static final String EXPECTED_RESULT = + StringUtils.lines( + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "0", + "0", + "0", + "0", + "-1", + "-1", + "0", + "0", + "0", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "2147483647", + "0", + "0", + "0", + "0", + "0", + "0", + "-1", + "-1", + "-1", + "0", + "0", + "0", + "0", + "2147483647", + "2147483647", + "2147483647", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "0", + "0", + "0", + "0", + "-1", + "-1", + "0", + "0", + "0", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "2147483646", + "0", + "0", + "0", + "0", + "0", + "0", + "-1", + "-1", + "-1", + "0", + "0", + "0", + "0", + "2147483646", + "2147483646", + "2147483646", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "0", + "0", + "0", + "0", + "-1", + "-1", + "0", + "0", + "0", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483648", + "0", + "0", + "0", + "0", + "0", + "0", + "-1", + "-1", + "-1", + "0", + "0", + "0", + "0", + "-2147483648", + "-2147483648", + "-2147483648", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "0", + "0", + "0", + "0", + "-1", + "-1", + "0", + "0", + "0", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "-2147483647", + "0", + "0", + "0", + "0", + "0", + "0", + "-1", + "-1", + "-1", + "0", + "0", + "0", + "0", + "-2147483647", + "-2147483647", + "-2147483647", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "-1", + "-1", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "-1", + "-1", + "-1", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "-1", + "-1", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "-1", + "-1", + "-1", + "0", + "0", + "0", + "0", + "1", + "1", + "1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "0", + "0", + "0", + "0", + "-1", + "-1", + "0", + "0", + "0", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "-1", + "0", + "0", + "0", + "0", + "0", + "0", + "-1", + "-1", + "-1", + "0", + "0", + "0", + "0", + "-1", + "-1", + "-1"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build(); + } + + public IdentityAbsorbingIntTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testD8() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_RESULT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .setMinApi(parameters) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_RESULT); + } + + private void inspect(CodeInspector inspector) { + inspector + .clazz(Main.class) + .forAllMethods( + m -> + assertTrue( + m.streamInstructions() + .noneMatch(i -> i.isIntLogicalBinop() || i.isIntArithmeticBinop()))); + } + + static class Main { + + public static void main(String[] args) { + intTests(Integer.MAX_VALUE); + intTests(Integer.MAX_VALUE - 1); + intTests(Integer.MIN_VALUE); + intTests(Integer.MIN_VALUE + 1); + intTests(System.currentTimeMillis() > 0 ? 0 : 1); + intTests(System.currentTimeMillis() > 0 ? 1 : 9); + intTests(System.currentTimeMillis() > 0 ? -1 : 1); + } + + private static void intTests(int val) { + identityIntTest(val); + absorbingIntTest(val); + identityDoubleIntTest(val); + absorbingDoubleIntTest(val); + chainIntTest(val); + associativeIdentityIntTest(val); + } + + @NeverInline + private static void identityDoubleIntTest(int val) { + System.out.println(val + 0 + 0); + System.out.println(0 + val + 0); + System.out.println(0 + 0 + val); + System.out.println(val - 0 - 0); + System.out.println(val * 1 * 1); + System.out.println(1 * val * 1); + System.out.println(1 * 1 * val); + System.out.println(val / 1 / 1); + + System.out.println(val & -1 & -1); + System.out.println(-1 & val & -1); + System.out.println(-1 & -1 & val); + System.out.println(val | 0 | 0); + System.out.println(0 | val | 0); + System.out.println(0 | 0 | val); + System.out.println(val ^ 0 ^ 0); + System.out.println(0 ^ val ^ 0); + System.out.println(0 ^ 0 ^ val); + System.out.println(val << 0 << 0); + System.out.println(val >> 0 >> 0); + System.out.println(val >>> 0 >>> 0); + } + + @NeverInline + private static void identityIntTest(int val) { + System.out.println(val + 0); + System.out.println(0 + val); + System.out.println(val - 0); + System.out.println(val * 1); + System.out.println(1 * val); + System.out.println(val / 1); + + System.out.println(val & -1); + System.out.println(-1 & val); + System.out.println(val | 0); + System.out.println(0 | val); + System.out.println(val ^ 0); + System.out.println(0 ^ val); + System.out.println(val << 0); + System.out.println(val >> 0); + System.out.println(val >>> 0); + } + + @NeverInline + private static void associativeIdentityIntTest(int val) { + int minusOne = -1; + System.out.println(val + 1 + minusOne); + System.out.println(val + 1 - 1); + System.out.println(val - 1 - minusOne); + } + + @NeverInline + private static void absorbingDoubleIntTest(int val) { + System.out.println(val * 0 * 0); + System.out.println(0 * val * 0); + System.out.println(0 * 0 * val); + // val would need to be proven non zero. + // System.out.println(0 / val); + // System.out.println(0 % val); + + System.out.println(0 & 0 & val); + System.out.println(0 & val & 0); + System.out.println(val & 0 & 0); + System.out.println(-1 | -1 | val); + System.out.println(-1 | val | -1); + System.out.println(val | -1 | -1); + System.out.println(0 << 0 << val); + System.out.println(0 >> 0 >> val); + System.out.println(0 >>> 0 >>> val); + } + + @NeverInline + private static void absorbingIntTest(int val) { + System.out.println(val * 0); + System.out.println(0 * val); + // val would need to be proven non zero. + // System.out.println(0 / val); + // System.out.println(0 % val); + + System.out.println(0 & val); + System.out.println(val & 0); + System.out.println(-1 | val); + System.out.println(val | -1); + System.out.println(0 << val); + System.out.println(0 >> val); + System.out.println(0 >>> val); + } + + private static void chainIntTest(int val) { + int abs = System.currentTimeMillis() > 0 ? val * 0 : 0 * val; + System.out.println(abs * val); + } + } +} diff --git a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingLongTest.java similarity index 55% rename from src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java rename to src/test/java/com/android/tools/r8/ir/IdentityAbsorbingLongTest.java index 3f9a8b27fc..a1f0c1c3c1 100644 --- a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java +++ b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingLongTest.java @@ -17,409 +17,10 @@ import org.junit.runners.Parameterized; @RunWith(Parameterized.class) -public class IdentityAbsorbingTest extends TestBase { +public class IdentityAbsorbingLongTest extends TestBase { private static final String EXPECTED_RESULT = StringUtils.lines( - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "0", - "0", - "0", - "0", - "-1", - "-1", - "0", - "0", - "0", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "2147483647", - "0", - "0", - "0", - "0", - "0", - "0", - "-1", - "-1", - "-1", - "0", - "0", - "0", - "0", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "0", - "0", - "0", - "0", - "-1", - "-1", - "0", - "0", - "0", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "2147483646", - "0", - "0", - "0", - "0", - "0", - "0", - "-1", - "-1", - "-1", - "0", - "0", - "0", - "0", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "0", - "0", - "0", - "0", - "-1", - "-1", - "0", - "0", - "0", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "-2147483648", - "0", - "0", - "0", - "0", - "0", - "0", - "-1", - "-1", - "-1", - "0", - "0", - "0", - "0", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "0", - "0", - "0", - "0", - "-1", - "-1", - "0", - "0", - "0", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "-2147483647", - "0", - "0", - "0", - "0", - "0", - "0", - "-1", - "-1", - "-1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "-1", - "-1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "-1", - "-1", - "-1", - "0", - "0", - "0", - "0", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "0", - "0", - "0", - "0", - "-1", - "-1", - "0", - "0", - "0", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "-1", - "-1", - "-1", - "0", - "0", - "0", - "0", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "0", - "0", - "0", - "0", - "-1", - "-1", - "0", - "0", - "0", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "-1", - "0", - "0", - "0", - "0", - "0", - "0", - "-1", - "-1", - "-1", - "0", - "0", - "0", - "0", "9223372036854775807", "9223372036854775807", "9223372036854775807", @@ -476,6 +77,9 @@ public class IdentityAbsorbingTest extends TestBase { "0", "0", "0", + "9223372036854775807", + "9223372036854775807", + "9223372036854775807", "9223372036854775806", "9223372036854775806", "9223372036854775806", @@ -532,6 +136,9 @@ public class IdentityAbsorbingTest extends TestBase { "0", "0", "0", + "9223372036854775806", + "9223372036854775806", + "9223372036854775806", "-9223372036854775808", "-9223372036854775808", "-9223372036854775808", @@ -588,6 +195,9 @@ public class IdentityAbsorbingTest extends TestBase { "0", "0", "0", + "-9223372036854775808", + "-9223372036854775808", + "-9223372036854775808", "-9223372036854775807", "-9223372036854775807", "-9223372036854775807", @@ -644,6 +254,9 @@ public class IdentityAbsorbingTest extends TestBase { "0", "0", "0", + "-9223372036854775807", + "-9223372036854775807", + "-9223372036854775807", "0", "0", "0", @@ -700,6 +313,9 @@ public class IdentityAbsorbingTest extends TestBase { "0", "0", "0", + "0", + "0", + "0", "1", "1", "1", @@ -756,6 +372,9 @@ public class IdentityAbsorbingTest extends TestBase { "0", "0", "0", + "1", + "1", + "1", "-1", "-1", "-1", @@ -811,7 +430,10 @@ public class IdentityAbsorbingTest extends TestBase { "-1", "0", "0", - "0"); + "0", + "-1", + "-1", + "-1"); private final TestParameters parameters; @@ -820,7 +442,7 @@ public static TestParametersCollection data() { return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build(); } - public IdentityAbsorbingTest(TestParameters parameters) { + public IdentityAbsorbingLongTest(TestParameters parameters) { this.parameters = parameters; } @@ -852,25 +474,12 @@ private void inspect(CodeInspector inspector) { m -> assertTrue( m.streamInstructions() - .noneMatch( - i -> - i.isIntLogicalBinop() - || i.isLongLogicalBinop() - || i.isIntArithmeticBinop() - || i.isLongArithmeticBinop()))); + .noneMatch(i -> i.isLongArithmeticBinop() || i.isLongLogicalBinop()))); } static class Main { public static void main(String[] args) { - intTests(Integer.MAX_VALUE); - intTests(Integer.MAX_VALUE - 1); - intTests(Integer.MIN_VALUE); - intTests(Integer.MIN_VALUE + 1); - intTests(System.currentTimeMillis() > 0 ? 0 : 1); - intTests(System.currentTimeMillis() > 0 ? 1 : 9); - intTests(System.currentTimeMillis() > 0 ? -1 : 1); - longTests(Long.MAX_VALUE); longTests(Long.MAX_VALUE - 1); longTests(Long.MIN_VALUE); @@ -885,39 +494,7 @@ private static void longTests(long val) { absorbingLongTest(val); identityDoubleLongTest(val); absorbingDoubleLongTest(val); - } - - private static void intTests(int val) { - identityIntTest(val); - absorbingIntTest(val); - identityDoubleIntTest(val); - absorbingDoubleIntTest(val); - chainIntTest(val); - } - - @NeverInline - private static void identityDoubleIntTest(int val) { - System.out.println(val + 0 + 0); - System.out.println(0 + val + 0); - System.out.println(0 + 0 + val); - System.out.println(val - 0 - 0); - System.out.println(val * 1 * 1); - System.out.println(1 * val * 1); - System.out.println(1 * 1 * val); - System.out.println(val / 1 / 1); - - System.out.println(val & -1 & -1); - System.out.println(-1 & val & -1); - System.out.println(-1 & -1 & val); - System.out.println(val | 0 | 0); - System.out.println(0 | val | 0); - System.out.println(0 | 0 | val); - System.out.println(val ^ 0 ^ 0); - System.out.println(0 ^ val ^ 0); - System.out.println(0 ^ 0 ^ val); - System.out.println(val << 0 << 0); - System.out.println(val >> 0 >> 0); - System.out.println(val >>> 0 >>> 0); + associativeIdentityLongTest(val); } @NeverInline @@ -945,26 +522,6 @@ private static void identityDoubleLongTest(long val) { System.out.println(val >>> 0L >>> 0L); } - @NeverInline - private static void identityIntTest(int val) { - System.out.println(val + 0); - System.out.println(0 + val); - System.out.println(val - 0); - System.out.println(val * 1); - System.out.println(1 * val); - System.out.println(val / 1); - - System.out.println(val & -1); - System.out.println(-1 & val); - System.out.println(val | 0); - System.out.println(0 | val); - System.out.println(val ^ 0); - System.out.println(0 ^ val); - System.out.println(val << 0); - System.out.println(val >> 0); - System.out.println(val >>> 0); - } - @NeverInline private static void identityLongTest(long val) { System.out.println(val + 0L); @@ -986,23 +543,11 @@ private static void identityLongTest(long val) { } @NeverInline - private static void absorbingDoubleIntTest(int val) { - System.out.println(val * 0 * 0); - System.out.println(0 * val * 0); - System.out.println(0 * 0 * val); - // val would need to be proven non zero. - // System.out.println(0 / val); - // System.out.println(0 % val); - - System.out.println(0 & 0 & val); - System.out.println(0 & val & 0); - System.out.println(val & 0 & 0); - System.out.println(-1 | -1 | val); - System.out.println(-1 | val | -1); - System.out.println(val | -1 | -1); - System.out.println(0 << 0 << val); - System.out.println(0 >> 0 >> val); - System.out.println(0 >>> 0 >>> val); + private static void associativeIdentityLongTest(long val) { + long minusOne = -1L; + System.out.println(val + 1L + minusOne); + System.out.println(val + 1L - 1L); + System.out.println(val - 1L - minusOne); } @NeverInline @@ -1025,23 +570,6 @@ private static void absorbingDoubleLongTest(long val) { System.out.println(0L >>> 0L >>> val); } - @NeverInline - private static void absorbingIntTest(int val) { - System.out.println(val * 0); - System.out.println(0 * val); - // val would need to be proven non zero. - // System.out.println(0 / val); - // System.out.println(0 % val); - - System.out.println(0 & val); - System.out.println(val & 0); - System.out.println(-1 | val); - System.out.println(val | -1); - System.out.println(0 << val); - System.out.println(0 >> val); - System.out.println(0 >>> val); - } - @NeverInline private static void absorbingLongTest(long val) { System.out.println(val * 0L); @@ -1058,10 +586,5 @@ private static void absorbingLongTest(long val) { System.out.println(0L >> val); System.out.println(0L >>> val); } - - private static void chainIntTest(int val) { - int abs = System.currentTimeMillis() > 0 ? val * 0 : 0 * val; - System.out.println(abs * val); - } } } From 108828fa2d7f18c82c97d4b67f0b2276c5a879d4 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Thu, 8 Jun 2023 08:44:32 +0200 Subject: [PATCH 025/153] Reland "Keep single caller inlinees after inlining if reprocessing" This reverts commit ac7ca7ea02277793be74e5384512245a535775e2. Bug: b/285021603 Change-Id: I5fe1d76407ae6331b142591fe1a9bc086a0f9d80 --- .../callgraph/CallSiteInformation.java | 45 +++++++++++++++++-- .../r8/ir/optimize/MultiCallerInliner.java | 3 +- .../DefaultInliningReasonStrategy.java | 6 +-- .../SingleTargetAfterInliningTest.java | 12 ++--- .../SingleCallerBridgeStartupTest.java | 15 ++++++- 5 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java index c2e13afb6e..e412ed793e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java @@ -13,6 +13,8 @@ import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.Sets; +import java.util.IdentityHashMap; +import java.util.Map; import java.util.Set; public abstract class CallSiteInformation { @@ -23,6 +25,14 @@ public abstract class CallSiteInformation { *

For pinned methods (methods kept through Proguard keep rules) this will always answer * false. */ + public abstract boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context); + + /** + * Checks if the given method only has a single call without considering context. + * + *

For pinned methods (methods kept through Proguard keep rules) and methods that override a + * library method this always returns false. + */ public abstract boolean hasSingleCallSite(ProgramMethod method); public abstract boolean isMultiCallerInlineCandidate(ProgramMethod method); @@ -37,6 +47,11 @@ private static class EmptyCallSiteInformation extends CallSiteInformation { private static final EmptyCallSiteInformation EMPTY_INFO = new EmptyCallSiteInformation(); + @Override + public boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context) { + return false; + } + @Override public boolean hasSingleCallSite(ProgramMethod method) { return false; @@ -55,7 +70,9 @@ public void unsetCallSiteInformation(ProgramMethod method) { static class CallGraphBasedCallSiteInformation extends CallSiteInformation { - private final Set singleCallerMethods = Sets.newIdentityHashSet(); + // Single callers track their calling context to ensure that the predicate is stable after + // inlining of the caller. + private final Map singleCallerMethods = new IdentityHashMap<>(); private final Set multiCallerInlineCandidates = Sets.newIdentityHashSet(); CallGraphBasedCallSiteInformation(AppView appView, CallGraph graph) { @@ -94,7 +111,16 @@ static class CallGraphBasedCallSiteInformation extends CallSiteInformation { int numberOfCallSites = node.getNumberOfCallSites(); if (numberOfCallSites == 1) { - singleCallerMethods.add(reference); + Set callersWithDeterministicOrder = node.getCallersWithDeterministicOrder(); + DexMethod caller = reference; + // We can have recursive methods where the recursive call is the only call site. We do + // not track callers for these. + if (!callersWithDeterministicOrder.isEmpty()) { + assert callersWithDeterministicOrder.size() == 1; + caller = callersWithDeterministicOrder.iterator().next().getMethod().getReference(); + } + DexMethod existing = singleCallerMethods.put(reference, caller); + assert existing == null; } else if (numberOfCallSites > 1) { multiCallerInlineCandidates.add(reference); } @@ -102,14 +128,25 @@ static class CallGraphBasedCallSiteInformation extends CallSiteInformation { } /** - * Checks if the given method only has a single call site. + * Checks if the given method only has a single call site with the given context. + * + *

For pinned methods (methods kept through Proguard keep rules) and methods that override a + * library method this always returns false. + */ + @Override + public boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context) { + return singleCallerMethods.get(method.getReference()) == context.getReference(); + } + + /** + * Checks if the given method only has a single call without considering context. * *

For pinned methods (methods kept through Proguard keep rules) and methods that override a * library method this always returns false. */ @Override public boolean hasSingleCallSite(ProgramMethod method) { - return singleCallerMethods.contains(method.getReference()); + return singleCallerMethods.containsKey(method.getReference()); } /** diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java index 54c8f61a01..4b5f0c984c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java @@ -147,12 +147,11 @@ void recordCallEdgeForMultiCallerInlining( // We track up to n call sites, where n is the size of multiCallerInliningInstructionLimits. if (callers.size() > multiCallerInliningInstructionLimits.length) { stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining( - method, singleTarget, methodProcessor, callers); + singleTarget, methodProcessor, callers); } } private void stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining( - ProgramMethod method, ProgramMethod singleTarget, MethodProcessor methodProcessor, ProgramMethodMultiset callers) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java index 537150e519..02d13dd2d3 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java @@ -53,7 +53,7 @@ public Reason computeInliningReason( // program. return Reason.SIMPLE; } - if (isSingleCallerInliningTarget(target)) { + if (isSingleCallerInliningTarget(target, context)) { return Reason.SINGLE_CALLER; } if (isMultiCallerInlineCandidate(invoke, target, oracle, methodProcessor)) { @@ -64,8 +64,8 @@ public Reason computeInliningReason( return Reason.SIMPLE; } - private boolean isSingleCallerInliningTarget(ProgramMethod method) { - if (!callSiteInformation.hasSingleCallSite(method)) { + private boolean isSingleCallerInliningTarget(ProgramMethod method, ProgramMethod context) { + if (!callSiteInformation.hasSingleCallSite(method, context)) { return false; } if (appView.appInfo().isNeverInlineDueToSingleCallerMethod(method)) { diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java index f5b5a470c8..706f8f0d77 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java @@ -75,13 +75,15 @@ private void inspect(CodeInspector inspector) { ClassSubject aClassSubject = inspector.clazz(A.class); assertThat(aClassSubject, isPresent()); - // B.foo() should be absent if the max inlining depth is 1, because indirection() has been - // inlined into main(), which makes B.foo() eligible for inlining into main(). + // B.foo() could potentially be absent if the max inlining depth is 1, because indirection() has + // been inlined into main(), which makes B.foo() eligible for inlining into main() since we can + // compute a single target. However, because indirection() can be inlined into multiple callers + // we cannot trust the single caller predicate. If indirection() also has a singler caller we + // could propagate the single call site property to B.foo(). ClassSubject bClassSubject = inspector.clazz(B.class); assertThat(bClassSubject, isPresent()); - assertThat( - bClassSubject.uniqueMethodWithOriginalName("foo"), - notIf(isPresent(), maxInliningDepth == 1)); + // TODO(b/286058449): We could inline this. + assertThat(bClassSubject.uniqueMethodWithOriginalName("foo"), isPresent()); // B.bar() should always be inlined because it is marked as @AlwaysInline. assertThat( diff --git a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java index 4a27e0fef6..1cb68de1c7 100644 --- a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java +++ b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java @@ -4,6 +4,9 @@ package com.android.tools.r8.startup; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -12,6 +15,7 @@ import com.android.tools.r8.startup.profile.ExternalStartupItem; import com.android.tools.r8.startup.profile.ExternalStartupMethod; import com.android.tools.r8.startup.utils.StartupTestingUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.junit.Test; @@ -55,9 +59,16 @@ public void test() throws Exception { (appView, inlinee, inliningDepth) -> inlinee.getMethodReference().equals(barMethod)) .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + // Assert that foo is not inlined. + ClassSubject A = inspector.clazz(A.class); + assertThat(A, isPresent()); + assertThat(A.uniqueMethodWithOriginalName("foo"), isPresent()); + }) .run(parameters.getRuntime(), Main.class) - // TODO(b/285021603): We should not fail here. - .assertFailureWithErrorThatThrows(NoSuchMethodError.class); + .assertSuccessWithOutputLines("A::foo", "A::foo"); } static class Main { From fd20233548b6a24d048070a544f93f1a3e159cbb Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Thu, 8 Jun 2023 08:49:57 +0200 Subject: [PATCH 026/153] [Partition] Add test for minified name matching a primitive type Bug: b/286001537 Change-Id: I7773bdab961493d6eba9e6470978cb053255bc89 --- ...oguardMapPartitionerOnClassNameToText.java | 2 +- ...RetracePartitionWithPrimitiveNameTest.java | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java index 3af7dff105..4fdc94ae07 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java @@ -273,7 +273,7 @@ public static class ProguardMapPartitionerBuilderImplInternal extends ProguardMapPartitionerBuilderImpl { private MappingPartitionKeyStrategy mappingPartitionKeyStrategy = - MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS; + MappingPartitionKeyStrategy.getDefaultStrategy(); public ProguardMapPartitionerBuilderImplInternal(DiagnosticsHandler diagnosticsHandler) { super(diagnosticsHandler); diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java new file mode 100644 index 0000000000..a7ee197e52 --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java @@ -0,0 +1,56 @@ +// Copyright (c) 2023, 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.retrace.partition; + +import static org.junit.Assert.assertThrows; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessagesImpl; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.retrace.ProguardMapProducer; +import com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy; +import com.android.tools.r8.retrace.internal.ProguardMapPartitionerOnClassNameToText.ProguardMapPartitionerBuilderImplInternal; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** Regression test for b/286001537. */ +@RunWith(Parameterized.class) +public class RetracePartitionWithPrimitiveNameTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public RetracePartitionWithPrimitiveNameTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + public final String mapping = + StringUtils.unixLines( + "# { id: 'com.android.tools.r8.mapping', version: '2.0' }", + "some.class1 -> int:", + " void field -> a"); + + @Test + public void testPartitionAndRetrace() { + ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping); + TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); + // TODO(b/286001537): We need to handle minified names matching primitive types. + assertThrows( + AssertionError.class, + () -> + new ProguardMapPartitionerBuilderImplInternal(diagnosticsHandler) + .setMappingPartitionKeyStrategy(MappingPartitionKeyStrategy.getDefaultStrategy()) + .setProguardMapProducer(proguardMapProducer) + .setPartitionConsumer(partition -> {}) + .build() + .run()); + } +} From 8fb508a1b2de10dbd3532b30923fec8fca83f2ed Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Thu, 8 Jun 2023 08:50:14 +0200 Subject: [PATCH 027/153] [Partition] Return empty package name for primitive types Bug: b/286001537 Change-Id: I740aba191f198ce0fc4c898f7be9aaa5d0a5e39b --- .../tools/r8/utils/DescriptorUtils.java | 4 +- ...RetracePartitionWithPrimitiveNameTest.java | 47 ++++++++++++++----- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java index 2c6f53559d..19b4d3889f 100644 --- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java +++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java @@ -456,8 +456,8 @@ assert isClassDescriptor(classDescriptor) : "Invalid class descriptor " * @return java package name i.e. "java.lang" */ public static String getPackageNameFromTypeName(String typeName) { - return getPackageNameFromBinaryName( - getClassBinaryNameFromDescriptor(javaTypeToDescriptor(typeName))); + int packageEndIndex = typeName.lastIndexOf(JAVA_PACKAGE_SEPARATOR); + return (packageEndIndex < 0) ? "" : typeName.substring(0, packageEndIndex); } /** diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java index a7ee197e52..e8487535ff 100644 --- a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java +++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java @@ -4,16 +4,24 @@ package com.android.tools.r8.retrace.partition; -import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertEquals; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestDiagnosticMessagesImpl; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.retrace.MappingPartitionMetadata; +import com.android.tools.r8.retrace.PartitionMappingSupplier; import com.android.tools.r8.retrace.ProguardMapProducer; +import com.android.tools.r8.retrace.RetraceOptions; +import com.android.tools.r8.retrace.RetraceStackFrameAmbiguousResultWithContext; +import com.android.tools.r8.retrace.RetraceStackTraceContext; +import com.android.tools.r8.retrace.StringRetrace; import com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy; import com.android.tools.r8.retrace.internal.ProguardMapPartitionerOnClassNameToText.ProguardMapPartitionerBuilderImplInternal; import com.android.tools.r8.utils.StringUtils; +import java.util.HashMap; +import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -36,21 +44,34 @@ public RetracePartitionWithPrimitiveNameTest(TestParameters parameters) { StringUtils.unixLines( "# { id: 'com.android.tools.r8.mapping', version: '2.0' }", "some.class1 -> int:", - " void field -> a"); + " void method() -> a"); @Test - public void testPartitionAndRetrace() { + public void testPartitionAndRetrace() throws Exception { ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping); TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); - // TODO(b/286001537): We need to handle minified names matching primitive types. - assertThrows( - AssertionError.class, - () -> - new ProguardMapPartitionerBuilderImplInternal(diagnosticsHandler) - .setMappingPartitionKeyStrategy(MappingPartitionKeyStrategy.getDefaultStrategy()) - .setProguardMapProducer(proguardMapProducer) - .setPartitionConsumer(partition -> {}) - .build() - .run()); + Map partitions = new HashMap<>(); + MappingPartitionMetadata metadata = + new ProguardMapPartitionerBuilderImplInternal(diagnosticsHandler) + .setMappingPartitionKeyStrategy(MappingPartitionKeyStrategy.getDefaultStrategy()) + .setProguardMapProducer(proguardMapProducer) + .setPartitionConsumer( + partition -> partitions.put(partition.getKey(), partition.getPayload())) + .build() + .run(); + + PartitionMappingSupplier mappingSupplier = + PartitionMappingSupplier.builder() + .setMetadata(metadata.getBytes()) + .setMappingPartitionFromKeySupplier(partitions::get) + .build(); + + StringRetrace retracer = + StringRetrace.create(RetraceOptions.builder().setMappingSupplier(mappingSupplier).build()); + RetraceStackFrameAmbiguousResultWithContext result = + retracer.retraceFrame(" at int.a()", RetraceStackTraceContext.empty()); + StringBuilder sb = new StringBuilder(); + result.forEach(frames -> frames.forEach(sb::append)); + assertEquals(" at some.class1.method(class1.java)", sb.toString()); } } From 3be92e691dae4e3160eb2fc6d5f156cae614967f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 8 Jun 2023 09:54:14 +0200 Subject: [PATCH 028/153] Split BranchSimplifier from CodeRewriter Bug: b/284304606 Change-Id: I2c25ca2255d90e2a5344988c77a0b8d56780283a --- .../tools/r8/ir/conversion/IRConverter.java | 5 +- .../r8/ir/conversion/IRToDexFinalizer.java | 6 +- .../conversion/passes/BranchSimplifier.java | 1090 +++++++++++++++++ .../passes}/SwitchCaseEliminator.java | 2 +- .../tools/r8/ir/optimize/CodeRewriter.java | 1040 ---------------- .../r8/ir/optimize/ConstantCanonicalizer.java | 10 +- .../tools/r8/ir/optimize/DeadCodeRemover.java | 5 +- .../RuntimeWorkaroundCodeRewriter.java | 105 +- .../optimize/classinliner/ClassInliner.java | 3 +- 9 files changed, 1160 insertions(+), 1106 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java rename src/main/java/com/android/tools/r8/ir/{optimize => conversion/passes}/SwitchCaseEliminator.java (99%) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 7636cbd109..a804771560 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -28,6 +28,7 @@ import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.passes.BinopRewriter; +import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; @@ -771,7 +772,7 @@ Timing optimize( codeRewriter.optimizeAlwaysThrowingInstructions(code); timing.end(); timing.begin("Simplify control flow"); - if (codeRewriter.simplifyControlFlow(code)) { + if (new BranchSimplifier(appView).simplifyBranches(code)) { timing.begin("Remove trivial type checks/casts"); codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( code, context, methodProcessor, methodProcessingContext); @@ -877,7 +878,7 @@ Timing optimize( if (!options.isGeneratingClassFiles()) { timing.begin("Canonicalize constants"); ConstantCanonicalizer constantCanonicalizer = - new ConstantCanonicalizer(appView, codeRewriter, context, code); + new ConstantCanonicalizer(appView, context, code); constantCanonicalizer.canonicalize(); timing.end(); previous = printMethod(code, "IR after constant canonicalization (SSA)", previous); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java index 06f3da8675..b7981b96cf 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java @@ -22,13 +22,11 @@ public class IRToDexFinalizer extends IRFinalizer { private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2; - - private final CodeRewriter codeRewriter; + private final InternalOptions options; public IRToDexFinalizer(AppView appView, DeadCodeRemover deadCodeRemover) { super(appView, deadCodeRemover); - this.codeRewriter = deadCodeRemover.getCodeRewriter(); this.options = appView.options(); } @@ -44,7 +42,7 @@ public DexCode finalizeCode( // Workaround massive dex2oat memory use for self-recursive methods. RuntimeWorkaroundCodeRewriter.workaroundDex2OatInliningIssue(appView, code); // Workaround MAX_INT switch issue. - RuntimeWorkaroundCodeRewriter.workaroundSwitchMaxIntBug(code, codeRewriter, options); + RuntimeWorkaroundCodeRewriter.workaroundSwitchMaxIntBug(code, appView); RuntimeWorkaroundCodeRewriter.workaroundDex2OatLinkedListBug(code, options); RuntimeWorkaroundCodeRewriter.workaroundForwardingInitializerBug(code, options); RuntimeWorkaroundCodeRewriter.workaroundExceptionTargetingLoopHeaderBug(code, options); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java new file mode 100644 index 0000000000..1cc873d002 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -0,0 +1,1090 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.FieldResolutionResult.SingleProgramFieldResolutionResult; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils; +import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; +import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.ir.analysis.value.ConstantOrNonConstantNumberValue; +import com.android.tools.r8.ir.analysis.value.SingleConstClassValue; +import com.android.tools.r8.ir.analysis.value.SingleFieldValue; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.ConstNumber; +import com.android.tools.r8.ir.code.Goto; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.If; +import com.android.tools.r8.ir.code.IfType; +import com.android.tools.r8.ir.code.InstanceGet; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionIterator; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.IntSwitch; +import com.android.tools.r8.ir.code.NumericType; +import com.android.tools.r8.ir.code.Phi; +import com.android.tools.r8.ir.code.Position; +import com.android.tools.r8.ir.code.Switch; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.code.ValueType; +import com.android.tools.r8.ir.code.Xor; +import com.android.tools.r8.ir.optimize.CodeRewriter.IfBuilder; +import com.android.tools.r8.ir.optimize.CodeRewriter.SwitchBuilder; +import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.InternalOutputMode; +import com.android.tools.r8.utils.LongInterval; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.PriorityQueue; +import java.util.Set; + +public class BranchSimplifier { + + private final AppView appView; + private final InternalOptions options; + + public BranchSimplifier(AppView appView) { + this.appView = appView; + this.options = appView.options(); + } + + public boolean simplifyBranches(IRCode code) { + boolean anyAffectedValues = rewriteSwitch(code); + anyAffectedValues |= simplifyIf(code).anyAffectedValues(); + return anyAffectedValues; + } + + public ControlFlowSimplificationResult simplifyIf(IRCode code) { + BasicBlockBehavioralSubsumption behavioralSubsumption = + new BasicBlockBehavioralSubsumption(appView, code); + boolean simplified = false; + for (BasicBlock block : code.blocks) { + // Skip removed (= unreachable) blocks. + if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) { + continue; + } + if (block.exit().isIf()) { + flipIfBranchesIfNeeded(code, block); + if (rewriteIfWithConstZero(code, block)) { + simplified = true; + } + + if (simplifyKnownBooleanCondition(code, block)) { + simplified = true; + if (!block.exit().isIf()) { + continue; + } + } + + // Simplify if conditions when possible. + If theIf = block.exit().asIf(); + if (theIf.isZeroTest()) { + if (simplifyIfZeroTest(code, block, theIf)) { + simplified = true; + continue; + } + } else { + if (simplifyNonIfZeroTest(code, block, theIf)) { + simplified = true; + continue; + } + } + + // Unable to determine which branch will be taken. Check if the true target can safely be + // rewritten to the false target. + if (behavioralSubsumption.isSubsumedBy( + theIf.inValues().get(0), theIf.getTrueTarget(), theIf.fallthroughBlock())) { + simplifyIfWithKnownCondition(code, block, theIf, theIf.fallthroughBlock()); + simplified = true; + } + } + } + Set affectedValues = code.removeUnreachableBlocks(); + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } + assert code.isConsistentSSA(appView); + return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified); + } + + public static class ControlFlowSimplificationResult { + + private final boolean anyAffectedValues; + private final boolean anySimplifications; + + private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySimplifications) { + assert !anyAffectedValues || anySimplifications; + this.anyAffectedValues = anyAffectedValues; + this.anySimplifications = anySimplifications; + } + + public boolean anyAffectedValues() { + return anyAffectedValues; + } + + public boolean anySimplifications() { + return anySimplifications; + } + } + + private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) { + Value lhs = theIf.lhs(); + Value lhsRoot = lhs.getAliasedValue(); + if (lhsRoot.isConstNumber()) { + ConstNumber cond = lhsRoot.getConstInstruction().asConstNumber(); + BasicBlock target = theIf.targetFromCondition(cond); + simplifyIfWithKnownCondition(code, block, theIf, target); + return true; + } + + if (theIf.isNullTest()) { + assert theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE; + + if (lhs.isAlwaysNull(appView)) { + simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNullObject()); + return true; + } + + if (lhs.isNeverNull()) { + simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNonNullObject()); + return true; + } + } + + if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) { + AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context()); + if (lhsAbstractValue.isConstantOrNonConstantNumberValue() + && !lhsAbstractValue.asConstantOrNonConstantNumberValue().containsInt(0)) { + // Value doesn't contain zero at all. + simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1)); + return true; + } + } + + if (lhs.hasValueRange()) { + LongInterval interval = lhs.getValueRange(); + if (!interval.containsValue(0)) { + // Interval doesn't contain zero at all. + int sign = Long.signum(interval.getMin()); + simplifyIfWithKnownCondition(code, block, theIf, sign); + return true; + } + + // Interval contains zero. + switch (theIf.getType()) { + case GE: + case LT: + // [a, b] >= 0 is always true if a >= 0. + // [a, b] < 0 is always false if a >= 0. + // In both cases a zero condition takes the right branch. + if (interval.getMin() == 0) { + simplifyIfWithKnownCondition(code, block, theIf, 0); + return true; + } + break; + + case LE: + case GT: + // [a, b] <= 0 is always true if b <= 0. + // [a, b] > 0 is always false if b <= 0. + // In both cases a zero condition takes the right branch. + if (interval.getMax() == 0) { + simplifyIfWithKnownCondition(code, block, theIf, 0); + return true; + } + break; + + case EQ: + case NE: + // Only a single element interval [0, 0] can be dealt with here. + // Such intervals should have been replaced by constants. + assert !interval.isSingleValue(); + break; + } + } + return false; + } + + private boolean simplifyNonIfZeroTest(IRCode code, BasicBlock block, If theIf) { + Value lhs = theIf.lhs(); + Value lhsRoot = lhs.getAliasedValue(); + Value rhs = theIf.rhs(); + Value rhsRoot = rhs.getAliasedValue(); + if (lhsRoot == rhsRoot) { + // Comparing the same value. + simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(0)); + return true; + } + + if (lhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray) + && rhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)) { + // Comparing two newly created objects. + assert theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE; + simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1)); + return true; + } + + if (lhsRoot.isConstNumber() && rhsRoot.isConstNumber()) { + // Zero test with a constant of comparison between between two constants. + ConstNumber left = lhsRoot.getConstInstruction().asConstNumber(); + ConstNumber right = rhsRoot.getConstInstruction().asConstNumber(); + BasicBlock target = theIf.targetFromCondition(left, right); + simplifyIfWithKnownCondition(code, block, theIf, target); + return true; + } + + if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) { + AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context()); + AbstractValue rhsAbstractValue = rhs.getAbstractValue(appView, code.context()); + if (lhsAbstractValue.isConstantOrNonConstantNumberValue() + && rhsAbstractValue.isConstantOrNonConstantNumberValue()) { + ConstantOrNonConstantNumberValue lhsNumberValue = + lhsAbstractValue.asConstantOrNonConstantNumberValue(); + ConstantOrNonConstantNumberValue rhsNumberValue = + rhsAbstractValue.asConstantOrNonConstantNumberValue(); + if (!lhsNumberValue.mayOverlapWith(rhsNumberValue)) { + // No overlap. + simplifyIfWithKnownCondition(code, block, theIf, 1); + return true; + } + } + } + + if (lhs.hasValueRange() && rhs.hasValueRange()) { + // Zero test with a value range, or comparison between between two values, + // each with a value ranges. + LongInterval leftRange = lhs.getValueRange(); + LongInterval rightRange = rhs.getValueRange(); + // Two overlapping ranges. Check for single point overlap. + if (!leftRange.overlapsWith(rightRange)) { + // No overlap. + int cond = Long.signum(leftRange.getMin() - rightRange.getMin()); + simplifyIfWithKnownCondition(code, block, theIf, cond); + return true; + } + + // The two intervals overlap. We can simplify if they overlap at the end points. + switch (theIf.getType()) { + case LT: + case GE: + // [a, b] < [c, d] is always false when a == d. + // [a, b] >= [c, d] is always true when a == d. + // In both cases 0 condition will choose the right branch. + if (leftRange.getMin() == rightRange.getMax()) { + simplifyIfWithKnownCondition(code, block, theIf, 0); + return true; + } + break; + case GT: + case LE: + // [a, b] > [c, d] is always false when b == c. + // [a, b] <= [c, d] is always true when b == c. + // In both cases 0 condition will choose the right branch. + if (leftRange.getMax() == rightRange.getMin()) { + simplifyIfWithKnownCondition(code, block, theIf, 0); + return true; + } + break; + case EQ: + case NE: + // Since there is overlap EQ and NE cannot be determined. + break; + } + } + + if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) { + ProgramMethod context = code.context(); + AbstractValue abstractValue = lhs.getAbstractValue(appView, context); + if (abstractValue.isSingleConstClassValue()) { + AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context); + if (otherAbstractValue.isSingleConstClassValue()) { + SingleConstClassValue singleConstClassValue = abstractValue.asSingleConstClassValue(); + SingleConstClassValue otherSingleConstClassValue = + otherAbstractValue.asSingleConstClassValue(); + simplifyIfWithKnownCondition( + code, + block, + theIf, + BooleanUtils.intValue( + singleConstClassValue.getType() != otherSingleConstClassValue.getType())); + return true; + } + return false; + } + + if (abstractValue.isSingleFieldValue()) { + AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context); + if (otherAbstractValue.isSingleFieldValue()) { + SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue(); + SingleFieldValue otherSingleFieldValue = otherAbstractValue.asSingleFieldValue(); + if (singleFieldValue.getField() == otherSingleFieldValue.getField()) { + simplifyIfWithKnownCondition(code, block, theIf, 0); + return true; + } + + DexClass holder = appView.definitionForHolder(singleFieldValue.getField()); + DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder); + if (field != null && field.isEnum()) { + DexClass otherHolder = appView.definitionForHolder(otherSingleFieldValue.getField()); + DexEncodedField otherField = + otherSingleFieldValue.getField().lookupOnClass(otherHolder); + if (otherField != null && otherField.isEnum()) { + simplifyIfWithKnownCondition(code, block, theIf, 1); + return true; + } + } + } + } + } + + return false; + } + + private void simplifyIfWithKnownCondition( + IRCode code, BasicBlock block, If theIf, BasicBlock target) { + BasicBlock deadTarget = + target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget(); + rewriteIfToGoto(code, block, theIf, target, deadTarget); + } + + private void simplifyIfWithKnownCondition(IRCode code, BasicBlock block, If theIf, int cond) { + simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(cond)); + } + + /* Identify simple diamond shapes converting boolean true/false to 1/0. We consider the forms: + * + * (1) + * + * [dbg pos x] [dbg pos x] + * ifeqz booleanValue ifnez booleanValue + * / \ / \ + * [dbg pos x][dbg pos x] [dbg pos x][dbg pos x] + * [const 0] [const 1] [const 1] [const 0] + * goto goto goto goto + * \ / \ / + * phi(0, 1) phi(1, 0) + * + * which can be replaced by a fallthrough and the phi value can be replaced + * with the boolean value itself. + * + * (2) + * + * [dbg pos x] [dbg pos x] + * ifeqz booleanValue ifnez booleanValue + * / \ / \ + * [dbg pos x][dbg pos x] [dbg pos x][dbg pos x] + * [const 1] [const 0] [const 0] [const 1] + * goto goto goto goto + * \ / \ / + * phi(1, 0) phi(0, 1) + * + * which can be replaced by a fallthrough and the phi value can be replaced + * by an xor instruction which is smaller. + */ + private boolean simplifyKnownBooleanCondition(IRCode code, BasicBlock block) { + If theIf = block.exit().asIf(); + Value testValue = theIf.inValues().get(0); + if (theIf.isZeroTest() && testValue.knownToBeBoolean()) { + BasicBlock trueBlock = theIf.getTrueTarget(); + BasicBlock falseBlock = theIf.fallthroughBlock(); + if (isBlockSupportedBySimplifyKnownBooleanCondition(trueBlock) + && isBlockSupportedBySimplifyKnownBooleanCondition(falseBlock) + && trueBlock.getSuccessors().get(0) == falseBlock.getSuccessors().get(0)) { + BasicBlock targetBlock = trueBlock.getSuccessors().get(0); + if (targetBlock.getPredecessors().size() == 2) { + int trueIndex = targetBlock.getPredecessors().indexOf(trueBlock); + int falseIndex = trueIndex == 0 ? 1 : 0; + int deadPhis = 0; + // Locate the phis that have the same value as the boolean and replace them + // by the boolean in all users. + for (Phi phi : targetBlock.getPhis()) { + Value trueValue = phi.getOperand(trueIndex); + Value falseValue = phi.getOperand(falseIndex); + if (trueValue.isConstNumber() && falseValue.isConstNumber()) { + ConstNumber trueNumber = trueValue.getConstInstruction().asConstNumber(); + ConstNumber falseNumber = falseValue.getConstInstruction().asConstNumber(); + if ((theIf.getType() == IfType.EQ + && trueNumber.isIntegerZero() + && falseNumber.isIntegerOne()) + || (theIf.getType() == IfType.NE + && trueNumber.isIntegerOne() + && falseNumber.isIntegerZero())) { + phi.replaceUsers(testValue); + deadPhis++; + } else if ((theIf.getType() == IfType.NE + && trueNumber.isIntegerZero() + && falseNumber.isIntegerOne()) + || (theIf.getType() == IfType.EQ + && trueNumber.isIntegerOne() + && falseNumber.isIntegerZero())) { + Value newOutValue = code.createValue(phi.getType(), phi.getLocalInfo()); + ConstNumber cstToUse = trueNumber.isIntegerOne() ? trueNumber : falseNumber; + BasicBlock phiBlock = phi.getBlock(); + Position phiPosition = phiBlock.getPosition(); + int insertIndex = 0; + if (cstToUse.getBlock() == trueBlock || cstToUse.getBlock() == falseBlock) { + // The constant belongs to the block to remove, create a new one. + cstToUse = ConstNumber.copyOf(code, cstToUse); + cstToUse.setBlock(phiBlock); + cstToUse.setPosition(phiPosition); + phiBlock.getInstructions().add(insertIndex++, cstToUse); + } + phi.replaceUsers(newOutValue); + Instruction newInstruction = + Xor.create(NumericType.INT, newOutValue, testValue, cstToUse.outValue()); + newInstruction.setBlock(phiBlock); + // The xor is replacing a phi so it does not have an actual position. + newInstruction.setPosition(phiPosition); + phiBlock.listIterator(code, insertIndex).add(newInstruction); + deadPhis++; + } + } + } + // If all phis were removed, there is no need for the diamond shape anymore + // and it can be rewritten to a goto to one of the branches. + if (deadPhis == targetBlock.getPhis().size()) { + rewriteIfToGoto(code, block, theIf, trueBlock, falseBlock); + return true; + } + return deadPhis > 0; + } + } + } + return false; + } + + private boolean isBlockSupportedBySimplifyKnownBooleanCondition(BasicBlock b) { + if (b.isTrivialGoto()) { + return true; + } + + int instructionSize = b.getInstructions().size(); + if (b.exit().isGoto() && (instructionSize == 2 || instructionSize == 3)) { + Instruction constInstruction = b.getInstructions().get(instructionSize - 2); + if (constInstruction.isConstNumber()) { + if (!constInstruction.asConstNumber().isIntegerOne() + && !constInstruction.asConstNumber().isIntegerZero()) { + return false; + } + if (instructionSize == 2) { + return true; + } + Instruction firstInstruction = b.getInstructions().getFirst(); + if (firstInstruction.isDebugPosition()) { + assert b.getPredecessors().size() == 1; + BasicBlock predecessorBlock = b.getPredecessors().get(0); + InstructionIterator it = predecessorBlock.iterator(predecessorBlock.exit()); + Instruction previousPosition = null; + while (it.hasPrevious() && !(previousPosition = it.previous()).isDebugPosition()) { + // Intentionally empty. + } + if (previousPosition != null) { + return previousPosition.getPosition() == firstInstruction.getPosition(); + } + } + } + } + + return false; + } + + private void rewriteIfToGoto( + IRCode code, BasicBlock block, If theIf, BasicBlock target, BasicBlock deadTarget) { + deadTarget.unlinkSinglePredecessorSiblingsAllowed(); + assert theIf == block.exit(); + block.replaceLastInstruction(new Goto(), code); + assert block.exit().isGoto(); + assert block.exit().asGoto().getTarget() == target; + } + + private boolean rewriteIfWithConstZero(IRCode code, BasicBlock block) { + If theIf = block.exit().asIf(); + if (theIf.isZeroTest()) { + return false; + } + + Value leftValue = theIf.lhs(); + Value rightValue = theIf.rhs(); + if (leftValue.isConstNumber() || rightValue.isConstNumber()) { + if (leftValue.isConstNumber()) { + if (leftValue.getConstInstruction().asConstNumber().isZero()) { + If ifz = new If(theIf.getType().forSwappedOperands(), rightValue); + block.replaceLastInstruction(ifz, code); + assert block.exit() == ifz; + return true; + } + } else if (rightValue.getConstInstruction().asConstNumber().isZero()) { + If ifz = new If(theIf.getType(), leftValue); + block.replaceLastInstruction(ifz, code); + assert block.exit() == ifz; + return true; + } + } + + return false; + } + + private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) { + If theIf = block.exit().asIf(); + BasicBlock trueTarget = theIf.getTrueTarget(); + BasicBlock fallthrough = theIf.fallthroughBlock(); + assert trueTarget != fallthrough; + + if (!fallthrough.isSimpleAlwaysThrowingPath() || trueTarget.isSimpleAlwaysThrowingPath()) { + return false; + } + + // In case fall-through block always throws there is a good chance that it + // is created for error checks and 'trueTarget' represents most more common + // non-error case. Flipping the if in this case may result in faster code + // on older Android versions. + List inValues = theIf.inValues(); + If newIf = new If(theIf.getType().inverted(), inValues); + block.replaceLastInstruction(newIf, code); + block.swapSuccessors(trueTarget, fallthrough); + return true; + } + + private boolean rewriteSwitch(IRCode code) { + return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance()); + } + + private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + if (!options.isSwitchRewritingEnabled()) { + return false; + } + if (!code.metadata().mayHaveSwitch()) { + return false; + } + return rewriteSwitchFull(code, switchCaseAnalyzer); + } + + private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + boolean needToRemoveUnreachableBlocks = false; + ListIterator blocksIterator = code.listIterator(); + while (blocksIterator.hasNext()) { + BasicBlock block = blocksIterator.next(); + InstructionListIterator iterator = block.listIterator(code); + while (iterator.hasNext()) { + Instruction instruction = iterator.next(); + if (instruction.isSwitch()) { + Switch theSwitch = instruction.asSwitch(); + if (options.testing.enableDeadSwitchCaseElimination) { + SwitchCaseEliminator eliminator = + removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer); + if (eliminator.mayHaveIntroducedUnreachableBlocks()) { + needToRemoveUnreachableBlocks = true; + } + + iterator.previous(); + instruction = iterator.next(); + if (instruction.isGoto()) { + continue; + } + + assert instruction.isSwitch(); + theSwitch = instruction.asSwitch(); + } + if (theSwitch.isIntSwitch()) { + rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); + } + } + } + } + + // Rewriting of switches introduces new branching structure. It relies on critical edges + // being split on the way in but does not maintain this property. We therefore split + // critical edges at exit. + code.splitCriticalEdges(); + + Set affectedValues = + needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of(); + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } + assert code.isConsistentSSA(appView); + return !affectedValues.isEmpty(); + } + + public void rewriteSingleKeySwitchToIf( + IRCode code, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) { + // Rewrite the switch to an if. + int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex(); + int caseBlockIndex = theSwitch.targetBlockIndices()[0]; + if (fallthroughBlockIndex < caseBlockIndex) { + block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex); + } + If replacement; + if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) { + replacement = new If(IfType.EQ, theSwitch.value()); + } else { + Instruction labelConst = theSwitch.materializeFirstKey(appView, code); + labelConst.setPosition(theSwitch.getPosition()); + iterator.previous(); + iterator.add(labelConst); + Instruction dummy = iterator.next(); + assert dummy == theSwitch; + replacement = new If(IfType.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue())); + } + iterator.replaceCurrentInstruction(replacement); + } + + private void rewriteIntSwitch( + IRCode code, + ListIterator blockIterator, + BasicBlock block, + InstructionListIterator iterator, + IntSwitch theSwitch) { + if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) { + return; + } + + if (theSwitch.numberOfKeys() == 1) { + rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch); + return; + } + + // If there are more than 1 key, we use the following algorithm to find keys to combine. + // First, scan through the keys forward and combine each packed interval with the + // previous interval if it gives a net saving. + // Secondly, go through all created intervals and combine the ones without a saving into + // a single interval and keep a max number of packed switches. + // Finally, go through all intervals and check if the switch or part of the switch + // should be transformed to ifs. + + // Phase 1: Combine packed intervals. + InternalOutputMode mode = options.getInternalOutputMode(); + int[] keys = theSwitch.getKeys(); + int maxNumberOfIfsOrSwitches = 10; + PriorityQueue biggestPackedSavings = + new PriorityQueue<>((x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode))); + Set biggestPackedSet = new HashSet<>(); + List intervals = new ArrayList<>(); + int previousKey = keys[0]; + IntList currentKeys = new IntArrayList(); + currentKeys.add(previousKey); + Interval previousInterval = null; + for (int i = 1; i < keys.length; i++) { + int key = keys[i]; + if (((long) key - (long) previousKey) > 1) { + Interval current = new Interval(currentKeys); + Interval added = combineOrAddInterval(intervals, previousInterval, current); + if (added != current && biggestPackedSet.contains(previousInterval)) { + biggestPackedSet.remove(previousInterval); + biggestPackedSavings.remove(previousInterval); + } + tryAddToBiggestSavings( + biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches); + previousInterval = added; + currentKeys = new IntArrayList(); + } + currentKeys.add(key); + previousKey = key; + } + Interval current = new Interval(currentKeys); + Interval added = combineOrAddInterval(intervals, previousInterval, current); + if (added != current && biggestPackedSet.contains(previousInterval)) { + biggestPackedSet.remove(previousInterval); + biggestPackedSavings.remove(previousInterval); + } + tryAddToBiggestSavings(biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches); + + // Phase 2: combine sparse intervals into a single bin. + // Check if we should save a space for a sparse switch, if so, remove the switch with + // the smallest savings. + if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches + && maxNumberOfIfsOrSwitches < intervals.size()) { + biggestPackedSet.remove(biggestPackedSavings.poll()); + } + Interval sparse = null; + List newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches); + for (int i = 0; i < intervals.size(); i++) { + Interval interval = intervals.get(i); + if (biggestPackedSet.contains(interval)) { + newSwitches.add(interval); + } else if (sparse == null) { + sparse = interval; + newSwitches.add(sparse); + } else { + sparse.addInterval(interval); + } + } + + // Phase 3: at this point we are guaranteed to have the biggest saving switches + // in newIntervals, potentially with a switch combining the remaining intervals. + // Now we check to see if we can create any if's to reduce size. + IntList outliers = new IntArrayList(); + int outliersAsIfSize = + options.testing.enableSwitchToIfRewriting + ? findIfsForCandidates(newSwitches, theSwitch, outliers) + : 0; + + long newSwitchesSize = 0; + List newSwitchSequences = new ArrayList<>(newSwitches.size()); + for (Interval interval : newSwitches) { + newSwitchesSize += interval.estimatedSize(mode); + newSwitchSequences.add(interval.keys); + } + + long currentSize = IntSwitch.estimatedSize(mode, theSwitch.getKeys()); + if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) { + convertSwitchToSwitchAndIfs( + code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers); + } + } + + // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId + // field values (from horizontal class merging. See bug for more details. + private boolean disableSwitchToIfRewritingForClassIdComparisons(IntSwitch theSwitch) { + Value switchValue = theSwitch.value().getAliasedValue(); + if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) { + return false; + } + AppInfoWithLiveness appInfo = appView.appInfoWithLiveness(); + if (appInfo == null) { + return false; + } + InstanceGet instanceGet = switchValue.getDefinition().asInstanceGet(); + SingleProgramFieldResolutionResult resolutionResult = + appInfo.resolveField(instanceGet.getField()).asSingleProgramFieldResolutionResult(); + if (resolutionResult == null) { + return false; + } + DexEncodedField resolvedField = resolutionResult.getResolvedField(); + return HorizontalClassMergerUtils.isClassIdField(appView, resolvedField); + } + + private SwitchCaseEliminator removeUnnecessarySwitchCases( + IRCode code, + Switch theSwitch, + InstructionListIterator iterator, + SwitchCaseAnalyzer switchCaseAnalyzer) { + BasicBlock defaultTarget = theSwitch.fallthroughBlock(); + SwitchCaseEliminator eliminator = new SwitchCaseEliminator(theSwitch, iterator); + BasicBlockBehavioralSubsumption behavioralSubsumption = + new BasicBlockBehavioralSubsumption(appView, code); + + // Compute the set of switch cases that can be removed. + boolean hasSwitchCaseToDefaultRewrite = false; + AbstractValue switchAbstractValue = theSwitch.value().getAbstractValue(appView, code.context()); + for (int i = 0; i < theSwitch.numberOfKeys(); i++) { + BasicBlock targetBlock = theSwitch.targetBlock(i); + + if (switchCaseAnalyzer.switchCaseIsAlwaysHit(theSwitch, i)) { + eliminator.markSwitchCaseAsAlwaysHit(i); + break; + } + + // This switch case can be removed if the behavior of the target block is equivalent to the + // behavior of the default block, or if the switch case is unreachable. + if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)) { + eliminator.markSwitchCaseForRemoval(i); + } else if (behavioralSubsumption.isSubsumedBy( + theSwitch.value(), targetBlock, defaultTarget)) { + eliminator.markSwitchCaseForRemoval(i); + hasSwitchCaseToDefaultRewrite = true; + } + } + + if (eliminator.isFallthroughLive() + && !hasSwitchCaseToDefaultRewrite + && switchCaseAnalyzer.switchFallthroughIsNeverHit(theSwitch, switchAbstractValue)) { + eliminator.markSwitchFallthroughAsNeverHit(); + } + + eliminator.optimize(); + return eliminator; + } + + private static class Interval { + + private final IntList keys = new IntArrayList(); + + public Interval(IntList... allKeys) { + assert allKeys.length > 0; + for (IntList keys : allKeys) { + assert keys.size() > 0; + this.keys.addAll(keys); + } + } + + public int getMin() { + return keys.getInt(0); + } + + public int getMax() { + return keys.getInt(keys.size() - 1); + } + + public void addInterval(Interval other) { + assert getMax() < other.getMin(); + keys.addAll(other.keys); + } + + public long packedSavings(InternalOutputMode mode) { + long packedTargets = (long) getMax() - (long) getMin() + 1; + if (!IntSwitch.canBePacked(mode, packedTargets)) { + return Long.MIN_VALUE + 1; + } + long sparseCost = + IntSwitch.baseSparseSize(mode) + IntSwitch.sparsePayloadSize(mode, keys.size()); + long packedCost = + IntSwitch.basePackedSize(mode) + IntSwitch.packedPayloadSize(mode, packedTargets); + return sparseCost - packedCost; + } + + public long estimatedSize(InternalOutputMode mode) { + return IntSwitch.estimatedSize(mode, keys.toIntArray()); + } + } + + private Interval combineOrAddInterval( + List intervals, Interval previous, Interval current) { + // As a first iteration, we only combine intervals if their packed size is less than their + // sparse counterpart. In CF we will have to add a load and a jump which add to the + // stack map table (1 is the size of a same entry). + InternalOutputMode mode = options.getInternalOutputMode(); + int penalty = mode.isGeneratingClassFiles() ? 3 + 1 : 0; + if (previous == null) { + intervals.add(current); + return current; + } + Interval combined = new Interval(previous.keys, current.keys); + long packedSavings = combined.packedSavings(mode); + if (packedSavings <= 0 + || packedSavings < previous.estimatedSize(mode) + current.estimatedSize(mode) - penalty) { + intervals.add(current); + return current; + } else { + intervals.set(intervals.size() - 1, combined); + return combined; + } + } + + private void tryAddToBiggestSavings( + Set biggestPackedSet, + PriorityQueue intervals, + Interval toAdd, + int maximumNumberOfSwitches) { + assert !biggestPackedSet.contains(toAdd); + long savings = toAdd.packedSavings(options.getInternalOutputMode()); + if (savings <= 0) { + return; + } + if (intervals.size() < maximumNumberOfSwitches) { + intervals.add(toAdd); + biggestPackedSet.add(toAdd); + } else if (savings > intervals.peek().packedSavings(options.getInternalOutputMode())) { + intervals.add(toAdd); + biggestPackedSet.add(toAdd); + biggestPackedSet.remove(intervals.poll()); + } + } + + private int codeUnitMargin() { + return options.getInternalOutputMode().isGeneratingClassFiles() ? 3 : 1; + } + + private int findIfsForCandidates( + List newSwitches, IntSwitch theSwitch, IntList outliers) { + Set switchesToRemove = new HashSet<>(); + InternalOutputMode mode = options.getInternalOutputMode(); + int outliersAsIfSize = 0; + // The candidateForIfs is either an index to a switch that can be eliminated totally or a sparse + // where removing a key may produce a greater saving. It is only if keys are small in the packed + // switch that removing the keys makes sense (size wise). + for (Interval candidate : newSwitches) { + int maxIfBudget = 10; + long switchSize = candidate.estimatedSize(mode); + int sizeOfAllKeysAsIf = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), candidate.keys); + if (candidate.keys.size() <= maxIfBudget + && sizeOfAllKeysAsIf < switchSize - codeUnitMargin()) { + outliersAsIfSize += sizeOfAllKeysAsIf; + switchesToRemove.add(candidate); + outliers.addAll(candidate.keys); + continue; + } + // One could do something clever here, but we use a simple algorithm that use the fact that + // all keys are sorted in ascending order and that the smallest absolute value will give the + // best saving. + IntList candidateKeys = candidate.keys; + int smallestPosition = -1; + long smallest = Long.MAX_VALUE; + for (int i = 0; i < candidateKeys.size(); i++) { + long current = Math.abs((long) candidateKeys.getInt(i)); + if (current < smallest) { + smallestPosition = i; + smallest = current; + } + } + // Add as many keys forward and backward as we have budget and we decrease in size. + IntList ifKeys = new IntArrayList(); + ifKeys.add(candidateKeys.getInt(smallestPosition)); + long previousSavings = 0; + long currentSavings = + switchSize + - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys) + - IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size()); + int minIndex = smallestPosition - 1; + int maxIndex = smallestPosition + 1; + while (ifKeys.size() < maxIfBudget && currentSavings > previousSavings) { + if (minIndex >= 0 && maxIndex < candidateKeys.size()) { + long valMin = Math.abs((long) candidateKeys.getInt(minIndex)); + int valMax = Math.abs(candidateKeys.getInt(maxIndex)); + if (valMax <= valMin) { + ifKeys.add(candidateKeys.getInt(maxIndex++)); + } else { + ifKeys.add(candidateKeys.getInt(minIndex--)); + } + } else if (minIndex >= 0) { + ifKeys.add(candidateKeys.getInt(minIndex--)); + } else if (maxIndex < candidateKeys.size()) { + ifKeys.add(candidateKeys.getInt(maxIndex++)); + } else { + // No more elements to add as if's. + break; + } + previousSavings = currentSavings; + currentSavings = + switchSize + - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys) + - IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size()); + } + if (previousSavings >= currentSavings) { + // Remove the last added key since it did not contribute to savings. + int lastKey = ifKeys.getInt(ifKeys.size() - 1); + ifKeys.removeInt(ifKeys.size() - 1); + if (lastKey == candidateKeys.getInt(minIndex + 1)) { + minIndex++; + } else { + maxIndex--; + } + } + // Adjust pointers into the candidate keys. + minIndex++; + maxIndex--; + if (ifKeys.size() > 0) { + int ifsSize = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys); + long newSwitchSize = + IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size()); + if (newSwitchSize + ifsSize + codeUnitMargin() < switchSize) { + candidateKeys.removeElements(minIndex, maxIndex); + outliers.addAll(ifKeys); + outliersAsIfSize += ifsSize; + } + } + } + newSwitches.removeAll(switchesToRemove); + return outliersAsIfSize; + } + + private int sizeForKeysWrittenAsIfs(ValueType type, Collection keys) { + int ifsSize = If.estimatedSize(options.getInternalOutputMode()) * keys.size(); + // In Cf we also require a load as well (and a stack map entry) + if (options.getInternalOutputMode().isGeneratingClassFiles()) { + ifsSize += keys.size() * (3 + 1); + } + for (int k : keys) { + if (k != 0) { + ifsSize += ConstNumber.estimatedSize(options.getInternalOutputMode(), type, k); + } + } + return ifsSize; + } + + /** + * Covert the switch instruction to a sequence of if instructions checking for a specified set of + * keys, followed by a new switch with the remaining keys. + */ + // TODO(b/270398965): Replace LinkedList. + @SuppressWarnings("JdkObsolete") + public void convertSwitchToSwitchAndIfs( + IRCode code, + ListIterator blocksIterator, + BasicBlock originalBlock, + InstructionListIterator iterator, + IntSwitch theSwitch, + List switches, + IntList keysToRemove) { + + Position position = theSwitch.getPosition(); + + // Extract the information from the switch before removing it. + Int2ReferenceSortedMap keyToTarget = theSwitch.getKeyToTargetMap(); + + // Keep track of the current fallthrough, starting with the original. + BasicBlock fallthroughBlock = theSwitch.fallthroughBlock(); + + // Split the switch instruction into its own block and remove it. + iterator.previous(); + BasicBlock originalSwitchBlock = iterator.split(code, blocksIterator); + assert !originalSwitchBlock.hasCatchHandlers(); + assert originalSwitchBlock.getInstructions().size() == 1; + assert originalBlock.exit().isGoto(); + theSwitch.moveDebugValues(originalBlock.exit()); + blocksIterator.remove(); + theSwitch.getBlock().detachAllSuccessors(); + BasicBlock block = theSwitch.getBlock().unlinkSinglePredecessor(); + assert theSwitch.getBlock().getPredecessors().size() == 0; + assert theSwitch.getBlock().getSuccessors().size() == 0; + assert block == originalBlock; + + // Collect the new blocks for adding to the block list. + LinkedList newBlocks = new LinkedList<>(); + + // Build the switch-blocks backwards, to always have the fallthrough block in hand. + for (int i = switches.size() - 1; i >= 0; i--) { + SwitchBuilder switchBuilder = new SwitchBuilder(position); + switchBuilder.setValue(theSwitch.value()); + IntList keys = switches.get(i); + for (int j = 0; j < keys.size(); j++) { + int key = keys.getInt(j); + switchBuilder.addKeyAndTarget(key, keyToTarget.get(key)); + } + switchBuilder.setFallthrough(fallthroughBlock).setBlockNumber(code.getNextBlockNumber()); + BasicBlock newSwitchBlock = switchBuilder.build(code.metadata()); + newBlocks.addFirst(newSwitchBlock); + fallthroughBlock = newSwitchBlock; + } + + // Build the if-blocks backwards, to always have the fallthrough block in hand. + for (int i = keysToRemove.size() - 1; i >= 0; i--) { + int key = keysToRemove.getInt(i); + BasicBlock peeledOffTarget = keyToTarget.get(key); + IfBuilder ifBuilder = new IfBuilder(position, code); + ifBuilder + .setLeft(theSwitch.value()) + .setRight(key) + .setTarget(peeledOffTarget) + .setFallthrough(fallthroughBlock) + .setBlockNumber(code.getNextBlockNumber()); + BasicBlock ifBlock = ifBuilder.build(); + newBlocks.addFirst(ifBlock); + fallthroughBlock = ifBlock; + } + + // Finally link the block before the original switch to the new block sequence. + originalBlock.link(fallthroughBlock); + + // Finally add the blocks. + newBlocks.forEach(blocksIterator::add); + } +} diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java similarity index 99% rename from src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java rename to src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java index 11e0b51b51..cbda6309d0 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java @@ -2,7 +2,7 @@ // 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.ir.optimize; +package com.android.tools.r8.ir.conversion.passes; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.ir.code.BasicBlock; 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 26da1f1168..756f75bf3d 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 @@ -24,7 +24,6 @@ import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexClassAndMethod; -import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; @@ -32,10 +31,7 @@ import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.FieldResolutionResult.SingleProgramFieldResolutionResult; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils; -import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption; import com.android.tools.r8.ir.analysis.type.ArrayTypeElement; import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; import com.android.tools.r8.ir.analysis.type.Nullability; @@ -43,9 +39,6 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.type.TypeUtils; import com.android.tools.r8.ir.analysis.value.AbstractValue; -import com.android.tools.r8.ir.analysis.value.ConstantOrNonConstantNumberValue; -import com.android.tools.r8.ir.analysis.value.SingleConstClassValue; -import com.android.tools.r8.ir.analysis.value.SingleFieldValue; import com.android.tools.r8.ir.code.ArrayLength; import com.android.tools.r8.ir.code.ArrayPut; import com.android.tools.r8.ir.code.Assume; @@ -89,7 +82,6 @@ import com.android.tools.r8.ir.code.NewArrayEmpty; import com.android.tools.r8.ir.code.NewArrayFilledData; import com.android.tools.r8.ir.code.NewInstance; -import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.SyntheticPosition; @@ -98,24 +90,17 @@ import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.UnusedArgument; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.code.ValueType; -import com.android.tools.r8.ir.code.Xor; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; -import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions; -import com.android.tools.r8.utils.InternalOutputMode; import com.android.tools.r8.utils.LazyBox; -import com.android.tools.r8.utils.LongInterval; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.collect.Streams; @@ -126,9 +111,7 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; -import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntIterator; -import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; @@ -140,7 +123,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; @@ -150,7 +132,6 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; -import java.util.PriorityQueue; import java.util.Set; import java.util.function.Predicate; @@ -632,531 +613,6 @@ public BasicBlock build() { } } - /** - * Covert the switch instruction to a sequence of if instructions checking for a specified set of - * keys, followed by a new switch with the remaining keys. - */ - // TODO(b/270398965): Replace LinkedList. - @SuppressWarnings("JdkObsolete") - void convertSwitchToSwitchAndIfs( - IRCode code, - ListIterator blocksIterator, - BasicBlock originalBlock, - InstructionListIterator iterator, - IntSwitch theSwitch, - List switches, - IntList keysToRemove) { - - Position position = theSwitch.getPosition(); - - // Extract the information from the switch before removing it. - Int2ReferenceSortedMap keyToTarget = theSwitch.getKeyToTargetMap(); - - // Keep track of the current fallthrough, starting with the original. - BasicBlock fallthroughBlock = theSwitch.fallthroughBlock(); - - // Split the switch instruction into its own block and remove it. - iterator.previous(); - BasicBlock originalSwitchBlock = iterator.split(code, blocksIterator); - assert !originalSwitchBlock.hasCatchHandlers(); - assert originalSwitchBlock.getInstructions().size() == 1; - assert originalBlock.exit().isGoto(); - theSwitch.moveDebugValues(originalBlock.exit()); - blocksIterator.remove(); - theSwitch.getBlock().detachAllSuccessors(); - BasicBlock block = theSwitch.getBlock().unlinkSinglePredecessor(); - assert theSwitch.getBlock().getPredecessors().size() == 0; - assert theSwitch.getBlock().getSuccessors().size() == 0; - assert block == originalBlock; - - // Collect the new blocks for adding to the block list. - LinkedList newBlocks = new LinkedList<>(); - - // Build the switch-blocks backwards, to always have the fallthrough block in hand. - for (int i = switches.size() - 1; i >= 0; i--) { - SwitchBuilder switchBuilder = new SwitchBuilder(position); - switchBuilder.setValue(theSwitch.value()); - IntList keys = switches.get(i); - for (int j = 0; j < keys.size(); j++) { - int key = keys.getInt(j); - switchBuilder.addKeyAndTarget(key, keyToTarget.get(key)); - } - switchBuilder.setFallthrough(fallthroughBlock).setBlockNumber(code.getNextBlockNumber()); - BasicBlock newSwitchBlock = switchBuilder.build(code.metadata()); - newBlocks.addFirst(newSwitchBlock); - fallthroughBlock = newSwitchBlock; - } - - // Build the if-blocks backwards, to always have the fallthrough block in hand. - for (int i = keysToRemove.size() - 1; i >= 0; i--) { - int key = keysToRemove.getInt(i); - BasicBlock peeledOffTarget = keyToTarget.get(key); - IfBuilder ifBuilder = new IfBuilder(position, code); - ifBuilder - .setLeft(theSwitch.value()) - .setRight(key) - .setTarget(peeledOffTarget) - .setFallthrough(fallthroughBlock) - .setBlockNumber(code.getNextBlockNumber()); - BasicBlock ifBlock = ifBuilder.build(); - newBlocks.addFirst(ifBlock); - fallthroughBlock = ifBlock; - } - - // Finally link the block before the original switch to the new block sequence. - originalBlock.link(fallthroughBlock); - - // Finally add the blocks. - newBlocks.forEach(blocksIterator::add); - } - - private static class Interval { - - private final IntList keys = new IntArrayList(); - - public Interval(IntList... allKeys) { - assert allKeys.length > 0; - for (IntList keys : allKeys) { - assert keys.size() > 0; - this.keys.addAll(keys); - } - } - - public int getMin() { - return keys.getInt(0); - } - - public int getMax() { - return keys.getInt(keys.size() - 1); - } - - public void addInterval(Interval other) { - assert getMax() < other.getMin(); - keys.addAll(other.keys); - } - - public long packedSavings(InternalOutputMode mode) { - long packedTargets = (long) getMax() - (long) getMin() + 1; - if (!IntSwitch.canBePacked(mode, packedTargets)) { - return Long.MIN_VALUE + 1; - } - long sparseCost = - IntSwitch.baseSparseSize(mode) + IntSwitch.sparsePayloadSize(mode, keys.size()); - long packedCost = - IntSwitch.basePackedSize(mode) + IntSwitch.packedPayloadSize(mode, packedTargets); - return sparseCost - packedCost; - } - - public long estimatedSize(InternalOutputMode mode) { - return IntSwitch.estimatedSize(mode, keys.toIntArray()); - } - } - - private Interval combineOrAddInterval( - List intervals, Interval previous, Interval current) { - // As a first iteration, we only combine intervals if their packed size is less than their - // sparse counterpart. In CF we will have to add a load and a jump which add to the - // stack map table (1 is the size of a same entry). - InternalOutputMode mode = options.getInternalOutputMode(); - int penalty = mode.isGeneratingClassFiles() ? 3 + 1 : 0; - if (previous == null) { - intervals.add(current); - return current; - } - Interval combined = new Interval(previous.keys, current.keys); - long packedSavings = combined.packedSavings(mode); - if (packedSavings <= 0 - || packedSavings < previous.estimatedSize(mode) + current.estimatedSize(mode) - penalty) { - intervals.add(current); - return current; - } else { - intervals.set(intervals.size() - 1, combined); - return combined; - } - } - - private void tryAddToBiggestSavings( - Set biggestPackedSet, - PriorityQueue intervals, - Interval toAdd, - int maximumNumberOfSwitches) { - assert !biggestPackedSet.contains(toAdd); - long savings = toAdd.packedSavings(options.getInternalOutputMode()); - if (savings <= 0) { - return; - } - if (intervals.size() < maximumNumberOfSwitches) { - intervals.add(toAdd); - biggestPackedSet.add(toAdd); - } else if (savings > intervals.peek().packedSavings(options.getInternalOutputMode())) { - intervals.add(toAdd); - biggestPackedSet.add(toAdd); - biggestPackedSet.remove(intervals.poll()); - } - } - - private int sizeForKeysWrittenAsIfs(ValueType type, Collection keys) { - int ifsSize = If.estimatedSize(options.getInternalOutputMode()) * keys.size(); - // In Cf we also require a load as well (and a stack map entry) - if (options.getInternalOutputMode().isGeneratingClassFiles()) { - ifsSize += keys.size() * (3 + 1); - } - for (int k : keys) { - if (k != 0) { - ifsSize += ConstNumber.estimatedSize(options.getInternalOutputMode(), type, k); - } - } - return ifsSize; - } - - private int codeUnitMargin() { - return options.getInternalOutputMode().isGeneratingClassFiles() ? 3 : 1; - } - - private int findIfsForCandidates( - List newSwitches, IntSwitch theSwitch, IntList outliers) { - Set switchesToRemove = new HashSet<>(); - InternalOutputMode mode = options.getInternalOutputMode(); - int outliersAsIfSize = 0; - // The candidateForIfs is either an index to a switch that can be eliminated totally or a sparse - // where removing a key may produce a greater saving. It is only if keys are small in the packed - // switch that removing the keys makes sense (size wise). - for (Interval candidate : newSwitches) { - int maxIfBudget = 10; - long switchSize = candidate.estimatedSize(mode); - int sizeOfAllKeysAsIf = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), candidate.keys); - if (candidate.keys.size() <= maxIfBudget - && sizeOfAllKeysAsIf < switchSize - codeUnitMargin()) { - outliersAsIfSize += sizeOfAllKeysAsIf; - switchesToRemove.add(candidate); - outliers.addAll(candidate.keys); - continue; - } - // One could do something clever here, but we use a simple algorithm that use the fact that - // all keys are sorted in ascending order and that the smallest absolute value will give the - // best saving. - IntList candidateKeys = candidate.keys; - int smallestPosition = -1; - long smallest = Long.MAX_VALUE; - for (int i = 0; i < candidateKeys.size(); i++) { - long current = Math.abs((long) candidateKeys.getInt(i)); - if (current < smallest) { - smallestPosition = i; - smallest = current; - } - } - // Add as many keys forward and backward as we have budget and we decrease in size. - IntList ifKeys = new IntArrayList(); - ifKeys.add(candidateKeys.getInt(smallestPosition)); - long previousSavings = 0; - long currentSavings = - switchSize - - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys) - - IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size()); - int minIndex = smallestPosition - 1; - int maxIndex = smallestPosition + 1; - while (ifKeys.size() < maxIfBudget && currentSavings > previousSavings) { - if (minIndex >= 0 && maxIndex < candidateKeys.size()) { - long valMin = Math.abs((long) candidateKeys.getInt(minIndex)); - int valMax = Math.abs(candidateKeys.getInt(maxIndex)); - if (valMax <= valMin) { - ifKeys.add(candidateKeys.getInt(maxIndex++)); - } else { - ifKeys.add(candidateKeys.getInt(minIndex--)); - } - } else if (minIndex >= 0) { - ifKeys.add(candidateKeys.getInt(minIndex--)); - } else if (maxIndex < candidateKeys.size()) { - ifKeys.add(candidateKeys.getInt(maxIndex++)); - } else { - // No more elements to add as if's. - break; - } - previousSavings = currentSavings; - currentSavings = - switchSize - - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys) - - IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size()); - } - if (previousSavings >= currentSavings) { - // Remove the last added key since it did not contribute to savings. - int lastKey = ifKeys.getInt(ifKeys.size() - 1); - ifKeys.removeInt(ifKeys.size() - 1); - if (lastKey == candidateKeys.getInt(minIndex + 1)) { - minIndex++; - } else { - maxIndex--; - } - } - // Adjust pointers into the candidate keys. - minIndex++; - maxIndex--; - if (ifKeys.size() > 0) { - int ifsSize = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys); - long newSwitchSize = - IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size()); - if (newSwitchSize + ifsSize + codeUnitMargin() < switchSize) { - candidateKeys.removeElements(minIndex, maxIndex); - outliers.addAll(ifKeys); - outliersAsIfSize += ifsSize; - } - } - } - newSwitches.removeAll(switchesToRemove); - return outliersAsIfSize; - } - - public boolean rewriteSwitch(IRCode code) { - return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance()); - } - - private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { - if (!options.isSwitchRewritingEnabled()) { - return false; - } - if (!code.metadata().mayHaveSwitch()) { - return false; - } - return rewriteSwitchFull(code, switchCaseAnalyzer); - } - - private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { - boolean needToRemoveUnreachableBlocks = false; - ListIterator blocksIterator = code.listIterator(); - while (blocksIterator.hasNext()) { - BasicBlock block = blocksIterator.next(); - InstructionListIterator iterator = block.listIterator(code); - while (iterator.hasNext()) { - Instruction instruction = iterator.next(); - if (instruction.isSwitch()) { - Switch theSwitch = instruction.asSwitch(); - if (options.testing.enableDeadSwitchCaseElimination) { - SwitchCaseEliminator eliminator = - removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer); - if (eliminator.mayHaveIntroducedUnreachableBlocks()) { - needToRemoveUnreachableBlocks = true; - } - - iterator.previous(); - instruction = iterator.next(); - if (instruction.isGoto()) { - continue; - } - - assert instruction.isSwitch(); - theSwitch = instruction.asSwitch(); - } - if (theSwitch.isIntSwitch()) { - rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); - } - } - } - } - - // Rewriting of switches introduces new branching structure. It relies on critical edges - // being split on the way in but does not maintain this property. We therefore split - // critical edges at exit. - code.splitCriticalEdges(); - - Set affectedValues = - needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of(); - if (!affectedValues.isEmpty()) { - new TypeAnalysis(appView).narrowing(affectedValues); - } - assert code.isConsistentSSA(appView); - return !affectedValues.isEmpty(); - } - - void rewriteSingleKeySwitchToIf( - IRCode code, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) { - // Rewrite the switch to an if. - int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex(); - int caseBlockIndex = theSwitch.targetBlockIndices()[0]; - if (fallthroughBlockIndex < caseBlockIndex) { - block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex); - } - If replacement; - if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) { - replacement = new If(IfType.EQ, theSwitch.value()); - } else { - Instruction labelConst = theSwitch.materializeFirstKey(appView, code); - labelConst.setPosition(theSwitch.getPosition()); - iterator.previous(); - iterator.add(labelConst); - Instruction dummy = iterator.next(); - assert dummy == theSwitch; - replacement = new If(IfType.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue())); - } - iterator.replaceCurrentInstruction(replacement); - } - - private void rewriteIntSwitch( - IRCode code, - ListIterator blockIterator, - BasicBlock block, - InstructionListIterator iterator, - IntSwitch theSwitch) { - if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) { - return; - } - - if (theSwitch.numberOfKeys() == 1) { - rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch); - return; - } - - // If there are more than 1 key, we use the following algorithm to find keys to combine. - // First, scan through the keys forward and combine each packed interval with the - // previous interval if it gives a net saving. - // Secondly, go through all created intervals and combine the ones without a saving into - // a single interval and keep a max number of packed switches. - // Finally, go through all intervals and check if the switch or part of the switch - // should be transformed to ifs. - - // Phase 1: Combine packed intervals. - InternalOutputMode mode = options.getInternalOutputMode(); - int[] keys = theSwitch.getKeys(); - int maxNumberOfIfsOrSwitches = 10; - PriorityQueue biggestPackedSavings = - new PriorityQueue<>((x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode))); - Set biggestPackedSet = new HashSet<>(); - List intervals = new ArrayList<>(); - int previousKey = keys[0]; - IntList currentKeys = new IntArrayList(); - currentKeys.add(previousKey); - Interval previousInterval = null; - for (int i = 1; i < keys.length; i++) { - int key = keys[i]; - if (((long) key - (long) previousKey) > 1) { - Interval current = new Interval(currentKeys); - Interval added = combineOrAddInterval(intervals, previousInterval, current); - if (added != current && biggestPackedSet.contains(previousInterval)) { - biggestPackedSet.remove(previousInterval); - biggestPackedSavings.remove(previousInterval); - } - tryAddToBiggestSavings( - biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches); - previousInterval = added; - currentKeys = new IntArrayList(); - } - currentKeys.add(key); - previousKey = key; - } - Interval current = new Interval(currentKeys); - Interval added = combineOrAddInterval(intervals, previousInterval, current); - if (added != current && biggestPackedSet.contains(previousInterval)) { - biggestPackedSet.remove(previousInterval); - biggestPackedSavings.remove(previousInterval); - } - tryAddToBiggestSavings(biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches); - - // Phase 2: combine sparse intervals into a single bin. - // Check if we should save a space for a sparse switch, if so, remove the switch with - // the smallest savings. - if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches - && maxNumberOfIfsOrSwitches < intervals.size()) { - biggestPackedSet.remove(biggestPackedSavings.poll()); - } - Interval sparse = null; - List newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches); - for (int i = 0; i < intervals.size(); i++) { - Interval interval = intervals.get(i); - if (biggestPackedSet.contains(interval)) { - newSwitches.add(interval); - } else if (sparse == null) { - sparse = interval; - newSwitches.add(sparse); - } else { - sparse.addInterval(interval); - } - } - - // Phase 3: at this point we are guaranteed to have the biggest saving switches - // in newIntervals, potentially with a switch combining the remaining intervals. - // Now we check to see if we can create any if's to reduce size. - IntList outliers = new IntArrayList(); - int outliersAsIfSize = - appView.options().testing.enableSwitchToIfRewriting - ? findIfsForCandidates(newSwitches, theSwitch, outliers) - : 0; - - long newSwitchesSize = 0; - List newSwitchSequences = new ArrayList<>(newSwitches.size()); - for (Interval interval : newSwitches) { - newSwitchesSize += interval.estimatedSize(mode); - newSwitchSequences.add(interval.keys); - } - - long currentSize = IntSwitch.estimatedSize(mode, theSwitch.getKeys()); - if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) { - convertSwitchToSwitchAndIfs( - code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers); - } - } - - // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId - // field values (from horizontal class merging. See bug for more details. - private boolean disableSwitchToIfRewritingForClassIdComparisons(IntSwitch theSwitch) { - Value switchValue = theSwitch.value().getAliasedValue(); - if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) { - return false; - } - AppInfoWithLiveness appInfo = appView.appInfoWithLiveness(); - if (appInfo == null) { - return false; - } - InstanceGet instanceGet = switchValue.getDefinition().asInstanceGet(); - SingleProgramFieldResolutionResult resolutionResult = - appInfo.resolveField(instanceGet.getField()).asSingleProgramFieldResolutionResult(); - if (resolutionResult == null) { - return false; - } - DexEncodedField resolvedField = resolutionResult.getResolvedField(); - return HorizontalClassMergerUtils.isClassIdField(appView, resolvedField); - } - - private SwitchCaseEliminator removeUnnecessarySwitchCases( - IRCode code, - Switch theSwitch, - InstructionListIterator iterator, - SwitchCaseAnalyzer switchCaseAnalyzer) { - BasicBlock defaultTarget = theSwitch.fallthroughBlock(); - SwitchCaseEliminator eliminator = new SwitchCaseEliminator(theSwitch, iterator); - BasicBlockBehavioralSubsumption behavioralSubsumption = - new BasicBlockBehavioralSubsumption(appView, code); - - // Compute the set of switch cases that can be removed. - boolean hasSwitchCaseToDefaultRewrite = false; - AbstractValue switchAbstractValue = theSwitch.value().getAbstractValue(appView, code.context()); - for (int i = 0; i < theSwitch.numberOfKeys(); i++) { - BasicBlock targetBlock = theSwitch.targetBlock(i); - - if (switchCaseAnalyzer.switchCaseIsAlwaysHit(theSwitch, i)) { - eliminator.markSwitchCaseAsAlwaysHit(i); - break; - } - - // This switch case can be removed if the behavior of the target block is equivalent to the - // behavior of the default block, or if the switch case is unreachable. - if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)) { - eliminator.markSwitchCaseForRemoval(i); - } else if (behavioralSubsumption.isSubsumedBy( - theSwitch.value(), targetBlock, defaultTarget)) { - eliminator.markSwitchCaseForRemoval(i); - hasSwitchCaseToDefaultRewrite = true; - } - } - - if (eliminator.isFallthroughLive() - && !hasSwitchCaseToDefaultRewrite - && switchCaseAnalyzer.switchFallthroughIsNeverHit(theSwitch, switchAbstractValue)) { - eliminator.markSwitchFallthroughAsNeverHit(); - } - - eliminator.optimize(); - return eliminator; - } - /** * Rewrite all branch targets to the destination of trivial goto chains when possible. Does not * rewrite fallthrough targets as that would require block reordering and the transformation only @@ -2597,309 +2053,6 @@ public void simplifyDebugLocals(IRCode code) { } } - static class ControlFlowSimplificationResult { - - private boolean anyAffectedValues; - private boolean anySimplifications; - - private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySimplifications) { - assert !anyAffectedValues || anySimplifications; - this.anyAffectedValues = anyAffectedValues; - this.anySimplifications = anySimplifications; - } - - public boolean anyAffectedValues() { - return anyAffectedValues; - } - - public boolean anySimplifications() { - return anySimplifications; - } - } - - public boolean simplifyControlFlow(IRCode code) { - boolean anyAffectedValues = rewriteSwitch(code); - anyAffectedValues |= simplifyIf(code).anyAffectedValues(); - return anyAffectedValues; - } - - public ControlFlowSimplificationResult simplifyIf(IRCode code) { - BasicBlockBehavioralSubsumption behavioralSubsumption = - new BasicBlockBehavioralSubsumption(appView, code); - boolean simplified = false; - for (BasicBlock block : code.blocks) { - // Skip removed (= unreachable) blocks. - if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) { - continue; - } - if (block.exit().isIf()) { - flipIfBranchesIfNeeded(code, block); - if (rewriteIfWithConstZero(code, block)) { - simplified = true; - } - - if (simplifyKnownBooleanCondition(code, block)) { - simplified = true; - if (!block.exit().isIf()) { - continue; - } - } - - // Simplify if conditions when possible. - If theIf = block.exit().asIf(); - if (theIf.isZeroTest()) { - if (simplifyIfZeroTest(code, block, theIf)) { - simplified = true; - continue; - } - } else { - if (simplifyNonIfZeroTest(code, block, theIf)) { - simplified = true; - continue; - } - } - - // Unable to determine which branch will be taken. Check if the true target can safely be - // rewritten to the false target. - if (behavioralSubsumption.isSubsumedBy( - theIf.inValues().get(0), theIf.getTrueTarget(), theIf.fallthroughBlock())) { - simplifyIfWithKnownCondition(code, block, theIf, theIf.fallthroughBlock()); - simplified = true; - } - } - } - Set affectedValues = code.removeUnreachableBlocks(); - if (!affectedValues.isEmpty()) { - new TypeAnalysis(appView).narrowing(affectedValues); - } - assert code.isConsistentSSA(appView); - return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified); - } - - private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) { - Value lhs = theIf.lhs(); - Value lhsRoot = lhs.getAliasedValue(); - if (lhsRoot.isConstNumber()) { - ConstNumber cond = lhsRoot.getConstInstruction().asConstNumber(); - BasicBlock target = theIf.targetFromCondition(cond); - simplifyIfWithKnownCondition(code, block, theIf, target); - return true; - } - - if (theIf.isNullTest()) { - assert theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE; - - if (lhs.isAlwaysNull(appView)) { - simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNullObject()); - return true; - } - - if (lhs.isNeverNull()) { - simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNonNullObject()); - return true; - } - } - - if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) { - AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context()); - if (lhsAbstractValue.isConstantOrNonConstantNumberValue() - && !lhsAbstractValue.asConstantOrNonConstantNumberValue().containsInt(0)) { - // Value doesn't contain zero at all. - simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1)); - return true; - } - } - - if (lhs.hasValueRange()) { - LongInterval interval = lhs.getValueRange(); - if (!interval.containsValue(0)) { - // Interval doesn't contain zero at all. - int sign = Long.signum(interval.getMin()); - simplifyIfWithKnownCondition(code, block, theIf, sign); - return true; - } - - // Interval contains zero. - switch (theIf.getType()) { - case GE: - case LT: - // [a, b] >= 0 is always true if a >= 0. - // [a, b] < 0 is always false if a >= 0. - // In both cases a zero condition takes the right branch. - if (interval.getMin() == 0) { - simplifyIfWithKnownCondition(code, block, theIf, 0); - return true; - } - break; - - case LE: - case GT: - // [a, b] <= 0 is always true if b <= 0. - // [a, b] > 0 is always false if b <= 0. - // In both cases a zero condition takes the right branch. - if (interval.getMax() == 0) { - simplifyIfWithKnownCondition(code, block, theIf, 0); - return true; - } - break; - - case EQ: - case NE: - // Only a single element interval [0, 0] can be dealt with here. - // Such intervals should have been replaced by constants. - assert !interval.isSingleValue(); - break; - } - } - return false; - } - - private boolean simplifyNonIfZeroTest(IRCode code, BasicBlock block, If theIf) { - Value lhs = theIf.lhs(); - Value lhsRoot = lhs.getAliasedValue(); - Value rhs = theIf.rhs(); - Value rhsRoot = rhs.getAliasedValue(); - if (lhsRoot == rhsRoot) { - // Comparing the same value. - simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(0)); - return true; - } - - if (lhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray) - && rhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)) { - // Comparing two newly created objects. - assert theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE; - simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1)); - return true; - } - - if (lhsRoot.isConstNumber() && rhsRoot.isConstNumber()) { - // Zero test with a constant of comparison between between two constants. - ConstNumber left = lhsRoot.getConstInstruction().asConstNumber(); - ConstNumber right = rhsRoot.getConstInstruction().asConstNumber(); - BasicBlock target = theIf.targetFromCondition(left, right); - simplifyIfWithKnownCondition(code, block, theIf, target); - return true; - } - - if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) { - AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context()); - AbstractValue rhsAbstractValue = rhs.getAbstractValue(appView, code.context()); - if (lhsAbstractValue.isConstantOrNonConstantNumberValue() - && rhsAbstractValue.isConstantOrNonConstantNumberValue()) { - ConstantOrNonConstantNumberValue lhsNumberValue = - lhsAbstractValue.asConstantOrNonConstantNumberValue(); - ConstantOrNonConstantNumberValue rhsNumberValue = - rhsAbstractValue.asConstantOrNonConstantNumberValue(); - if (!lhsNumberValue.mayOverlapWith(rhsNumberValue)) { - // No overlap. - simplifyIfWithKnownCondition(code, block, theIf, 1); - return true; - } - } - } - - if (lhs.hasValueRange() && rhs.hasValueRange()) { - // Zero test with a value range, or comparison between between two values, - // each with a value ranges. - LongInterval leftRange = lhs.getValueRange(); - LongInterval rightRange = rhs.getValueRange(); - // Two overlapping ranges. Check for single point overlap. - if (!leftRange.overlapsWith(rightRange)) { - // No overlap. - int cond = Long.signum(leftRange.getMin() - rightRange.getMin()); - simplifyIfWithKnownCondition(code, block, theIf, cond); - return true; - } - - // The two intervals overlap. We can simplify if they overlap at the end points. - switch (theIf.getType()) { - case LT: - case GE: - // [a, b] < [c, d] is always false when a == d. - // [a, b] >= [c, d] is always true when a == d. - // In both cases 0 condition will choose the right branch. - if (leftRange.getMin() == rightRange.getMax()) { - simplifyIfWithKnownCondition(code, block, theIf, 0); - return true; - } - break; - case GT: - case LE: - // [a, b] > [c, d] is always false when b == c. - // [a, b] <= [c, d] is always true when b == c. - // In both cases 0 condition will choose the right branch. - if (leftRange.getMax() == rightRange.getMin()) { - simplifyIfWithKnownCondition(code, block, theIf, 0); - return true; - } - break; - case EQ: - case NE: - // Since there is overlap EQ and NE cannot be determined. - break; - } - } - - if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) { - ProgramMethod context = code.context(); - AbstractValue abstractValue = lhs.getAbstractValue(appView, context); - if (abstractValue.isSingleConstClassValue()) { - AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context); - if (otherAbstractValue.isSingleConstClassValue()) { - SingleConstClassValue singleConstClassValue = abstractValue.asSingleConstClassValue(); - SingleConstClassValue otherSingleConstClassValue = - otherAbstractValue.asSingleConstClassValue(); - simplifyIfWithKnownCondition( - code, - block, - theIf, - BooleanUtils.intValue( - singleConstClassValue.getType() != otherSingleConstClassValue.getType())); - return true; - } - return false; - } - - if (abstractValue.isSingleFieldValue()) { - AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context); - if (otherAbstractValue.isSingleFieldValue()) { - SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue(); - SingleFieldValue otherSingleFieldValue = otherAbstractValue.asSingleFieldValue(); - if (singleFieldValue.getField() == otherSingleFieldValue.getField()) { - simplifyIfWithKnownCondition(code, block, theIf, 0); - return true; - } - - DexClass holder = appView.definitionForHolder(singleFieldValue.getField()); - DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder); - if (field != null && field.isEnum()) { - DexClass otherHolder = appView.definitionForHolder(otherSingleFieldValue.getField()); - DexEncodedField otherField = - otherSingleFieldValue.getField().lookupOnClass(otherHolder); - if (otherField != null && otherField.isEnum()) { - simplifyIfWithKnownCondition(code, block, theIf, 1); - return true; - } - } - } - } - } - - return false; - } - - private void simplifyIfWithKnownCondition( - IRCode code, BasicBlock block, If theIf, BasicBlock target) { - BasicBlock deadTarget = - target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget(); - rewriteIfToGoto(code, block, theIf, target, deadTarget); - } - - private void simplifyIfWithKnownCondition(IRCode code, BasicBlock block, If theIf, int cond) { - simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(cond)); - } - /** * This optimization exploits that we can sometimes learn the constant value of an SSA value that * flows into an if-eq of if-neq instruction. @@ -3380,151 +2533,6 @@ public void optimizeAlwaysThrowingInstructions(IRCode code) { assert code.isConsistentSSA(appView); } - /* Identify simple diamond shapes converting boolean true/false to 1/0. We consider the forms: - * - * (1) - * - * [dbg pos x] [dbg pos x] - * ifeqz booleanValue ifnez booleanValue - * / \ / \ - * [dbg pos x][dbg pos x] [dbg pos x][dbg pos x] - * [const 0] [const 1] [const 1] [const 0] - * goto goto goto goto - * \ / \ / - * phi(0, 1) phi(1, 0) - * - * which can be replaced by a fallthrough and the phi value can be replaced - * with the boolean value itself. - * - * (2) - * - * [dbg pos x] [dbg pos x] - * ifeqz booleanValue ifnez booleanValue - * / \ / \ - * [dbg pos x][dbg pos x] [dbg pos x][dbg pos x] - * [const 1] [const 0] [const 0] [const 1] - * goto goto goto goto - * \ / \ / - * phi(1, 0) phi(0, 1) - * - * which can be replaced by a fallthrough and the phi value can be replaced - * by an xor instruction which is smaller. - */ - private boolean simplifyKnownBooleanCondition(IRCode code, BasicBlock block) { - If theIf = block.exit().asIf(); - Value testValue = theIf.inValues().get(0); - if (theIf.isZeroTest() && testValue.knownToBeBoolean()) { - BasicBlock trueBlock = theIf.getTrueTarget(); - BasicBlock falseBlock = theIf.fallthroughBlock(); - if (isBlockSupportedBySimplifyKnownBooleanCondition(trueBlock) && - isBlockSupportedBySimplifyKnownBooleanCondition(falseBlock) && - trueBlock.getSuccessors().get(0) == falseBlock.getSuccessors().get(0)) { - BasicBlock targetBlock = trueBlock.getSuccessors().get(0); - if (targetBlock.getPredecessors().size() == 2) { - int trueIndex = targetBlock.getPredecessors().indexOf(trueBlock); - int falseIndex = trueIndex == 0 ? 1 : 0; - int deadPhis = 0; - // Locate the phis that have the same value as the boolean and replace them - // by the boolean in all users. - for (Phi phi : targetBlock.getPhis()) { - Value trueValue = phi.getOperand(trueIndex); - Value falseValue = phi.getOperand(falseIndex); - if (trueValue.isConstNumber() && falseValue.isConstNumber()) { - ConstNumber trueNumber = trueValue.getConstInstruction().asConstNumber(); - ConstNumber falseNumber = falseValue.getConstInstruction().asConstNumber(); - if ((theIf.getType() == IfType.EQ - && trueNumber.isIntegerZero() - && falseNumber.isIntegerOne()) - || (theIf.getType() == IfType.NE - && trueNumber.isIntegerOne() - && falseNumber.isIntegerZero())) { - phi.replaceUsers(testValue); - deadPhis++; - } else if ((theIf.getType() == IfType.NE - && trueNumber.isIntegerZero() - && falseNumber.isIntegerOne()) - || (theIf.getType() == IfType.EQ - && trueNumber.isIntegerOne() - && falseNumber.isIntegerZero())) { - Value newOutValue = code.createValue(phi.getType(), phi.getLocalInfo()); - ConstNumber cstToUse = trueNumber.isIntegerOne() ? trueNumber : falseNumber; - BasicBlock phiBlock = phi.getBlock(); - Position phiPosition = phiBlock.getPosition(); - int insertIndex = 0; - if (cstToUse.getBlock() == trueBlock || cstToUse.getBlock() == falseBlock) { - // The constant belongs to the block to remove, create a new one. - cstToUse = ConstNumber.copyOf(code, cstToUse); - cstToUse.setBlock(phiBlock); - cstToUse.setPosition(phiPosition); - phiBlock.getInstructions().add(insertIndex++, cstToUse); - } - phi.replaceUsers(newOutValue); - Instruction newInstruction = - Xor.create(NumericType.INT, newOutValue, testValue, cstToUse.outValue()); - newInstruction.setBlock(phiBlock); - // The xor is replacing a phi so it does not have an actual position. - newInstruction.setPosition(phiPosition); - phiBlock.listIterator(code, insertIndex).add(newInstruction); - deadPhis++; - } - } - } - // If all phis were removed, there is no need for the diamond shape anymore - // and it can be rewritten to a goto to one of the branches. - if (deadPhis == targetBlock.getPhis().size()) { - rewriteIfToGoto(code, block, theIf, trueBlock, falseBlock); - return true; - } - return deadPhis > 0; - } - } - } - return false; - } - - private boolean isBlockSupportedBySimplifyKnownBooleanCondition(BasicBlock b) { - if (b.isTrivialGoto()) { - return true; - } - - int instructionSize = b.getInstructions().size(); - if (b.exit().isGoto() && (instructionSize == 2 || instructionSize == 3)) { - Instruction constInstruction = b.getInstructions().get(instructionSize - 2); - if (constInstruction.isConstNumber()) { - if (!constInstruction.asConstNumber().isIntegerOne() && - !constInstruction.asConstNumber().isIntegerZero()) { - return false; - } - if (instructionSize == 2) { - return true; - } - Instruction firstInstruction = b.getInstructions().getFirst(); - if (firstInstruction.isDebugPosition()) { - assert b.getPredecessors().size() == 1; - BasicBlock predecessorBlock = b.getPredecessors().get(0); - InstructionIterator it = predecessorBlock.iterator(predecessorBlock.exit()); - Instruction previousPosition = null; - while (it.hasPrevious() && !(previousPosition = it.previous()).isDebugPosition()) { - // Intentionally empty. - } - if (previousPosition != null) { - return previousPosition.getPosition() == firstInstruction.getPosition(); - } - } - } - } - - return false; - } - - private void rewriteIfToGoto( - IRCode code, BasicBlock block, If theIf, BasicBlock target, BasicBlock deadTarget) { - deadTarget.unlinkSinglePredecessorSiblingsAllowed(); - assert theIf == block.exit(); - block.replaceLastInstruction(new Goto(), code); - assert block.exit().isGoto(); - assert block.exit().asGoto().getTarget() == target; - } private void insertNotNullCheck( BasicBlock block, @@ -3547,54 +2555,6 @@ private void insertNotNullCheck( assert block.exit().asGoto().getTarget() == target; } - private boolean rewriteIfWithConstZero(IRCode code, BasicBlock block) { - If theIf = block.exit().asIf(); - if (theIf.isZeroTest()) { - return false; - } - - Value leftValue = theIf.lhs(); - Value rightValue = theIf.rhs(); - if (leftValue.isConstNumber() || rightValue.isConstNumber()) { - if (leftValue.isConstNumber()) { - if (leftValue.getConstInstruction().asConstNumber().isZero()) { - If ifz = new If(theIf.getType().forSwappedOperands(), rightValue); - block.replaceLastInstruction(ifz, code); - assert block.exit() == ifz; - return true; - } - } else if (rightValue.getConstInstruction().asConstNumber().isZero()) { - If ifz = new If(theIf.getType(), leftValue); - block.replaceLastInstruction(ifz, code); - assert block.exit() == ifz; - return true; - } - } - - return false; - } - - private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) { - If theIf = block.exit().asIf(); - BasicBlock trueTarget = theIf.getTrueTarget(); - BasicBlock fallthrough = theIf.fallthroughBlock(); - assert trueTarget != fallthrough; - - if (!fallthrough.isSimpleAlwaysThrowingPath() || trueTarget.isSimpleAlwaysThrowingPath()) { - return false; - } - - // In case fall-through block always throws there is a good chance that it - // is created for error checks and 'trueTarget' represents most more common - // non-error case. Flipping the if in this case may result in faster code - // on older Android versions. - List inValues = theIf.inValues(); - If newIf = new If(theIf.getType().inverted(), inValues); - block.replaceLastInstruction(newIf, code); - block.swapSuccessors(trueTarget, fallthrough); - return true; - } - public void rewriteKnownArrayLengthCalls(IRCode code) { InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java index 677da9cc08..b4fa5a65e4 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java @@ -41,6 +41,7 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; @@ -65,17 +66,16 @@ public class ConstantCanonicalizer { private static final int MAX_CANONICALIZED_CONSTANT = 22; private final AppView appView; - private final CodeRewriter codeRewriter; + private final BranchSimplifier branchSimplifier; private final ProgramMethod context; private final IRCode code; private OptionalBool isAccessingVolatileField = OptionalBool.unknown(); private Set ineligibleInstanceGetInstructions; - public ConstantCanonicalizer( - AppView appView, CodeRewriter codeRewriter, ProgramMethod context, IRCode code) { + public ConstantCanonicalizer(AppView appView, ProgramMethod context, IRCode code) { this.appView = appView; - this.codeRewriter = codeRewriter; + this.branchSimplifier = new BranchSimplifier(appView); this.context = context; this.code = code; } @@ -288,7 +288,7 @@ public boolean equals(Instruction a, Instruction b) { shouldSimplifyIfs |= code.removeAllDeadAndTrivialPhis(); if (shouldSimplifyIfs) { - codeRewriter.simplifyIf(code); + branchSimplifier.simplifyIf(code); } assert code.isConsistentSSA(appView); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index b7d7b10470..fcefb79b1f 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java @@ -20,6 +20,7 @@ import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueIsDeadAnalysis; +import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.IterableUtils; @@ -50,6 +51,8 @@ public void run(IRCode code, Timing timing) { codeRewriter.rewriteMoveResult(code); + BranchSimplifier branchSimplifier = new BranchSimplifier(appView); + // We may encounter unneeded catch handlers after each iteration, e.g., if a dead instruction // is the only throwing instruction in a block. Removing unneeded catch handlers can lead to // more dead instructions. @@ -62,7 +65,7 @@ public void run(IRCode code, Timing timing) { removeDeadInstructions(worklist, code, block, valueIsDeadAnalysis); removeDeadPhis(worklist, block, valueIsDeadAnalysis); } - } while (codeRewriter.simplifyIf(code).anySimplifications() + } while (branchSimplifier.simplifyIf(code).anySimplifications() || removeUnneededCatchHandlers(code)); assert code.isConsistentSSA(appView); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java index afed4c23d6..370a5d74b8 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java @@ -22,6 +22,7 @@ import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; @@ -68,6 +69,58 @@ public static void workaroundDex2OatInliningIssue(AppView appView, IRCode cod } } + public static void workaroundSwitchMaxIntBug(IRCode code, AppView appView) { + if (appView.options().canHaveSwitchMaxIntBug() && code.metadata().mayHaveSwitch()) { + // Always rewrite for workaround switch bug. + rewriteSwitchForMaxIntOnly(code, appView); + } + } + + private static void rewriteSwitchForMaxIntOnly(IRCode code, AppView appView) { + boolean needToSplitCriticalEdges = false; + BranchSimplifier branchSimplifier = new BranchSimplifier(appView); + ListIterator blocksIterator = code.listIterator(); + while (blocksIterator.hasNext()) { + BasicBlock block = blocksIterator.next(); + InstructionListIterator iterator = block.listIterator(code); + while (iterator.hasNext()) { + Instruction instruction = iterator.next(); + assert !instruction.isStringSwitch(); + if (instruction.isIntSwitch()) { + IntSwitch intSwitch = instruction.asIntSwitch(); + if (intSwitch.getKey(intSwitch.numberOfKeys() - 1) == Integer.MAX_VALUE) { + if (intSwitch.numberOfKeys() == 1) { + branchSimplifier.rewriteSingleKeySwitchToIf(code, block, iterator, intSwitch); + } else { + IntList newSwitchSequences = new IntArrayList(intSwitch.numberOfKeys() - 1); + for (int i = 0; i < intSwitch.numberOfKeys() - 1; i++) { + newSwitchSequences.add(intSwitch.getKey(i)); + } + IntList outliers = new IntArrayList(1); + outliers.add(Integer.MAX_VALUE); + branchSimplifier.convertSwitchToSwitchAndIfs( + code, + blocksIterator, + block, + iterator, + intSwitch, + ImmutableList.of(newSwitchSequences), + outliers); + } + needToSplitCriticalEdges = true; + } + } + } + } + + // Rewriting of switches introduces new branching structure. It relies on critical edges + // being split on the way in but does not maintain this property. We therefore split + // critical edges at exit. + if (needToSplitCriticalEdges) { + code.splitCriticalEdges(); + } + } + /** * For each block, we look to see if the header matches: * @@ -192,58 +245,6 @@ public static void workaroundForwardingInitializerBug(IRCode code, InternalOptio } } - public static void workaroundSwitchMaxIntBug( - IRCode code, CodeRewriter codeRewriter, InternalOptions options) { - if (options.canHaveSwitchMaxIntBug() && code.metadata().mayHaveSwitch()) { - // Always rewrite for workaround switch bug. - rewriteSwitchForMaxIntOnly(code, codeRewriter); - } - } - - private static void rewriteSwitchForMaxIntOnly(IRCode code, CodeRewriter codeRewriter) { - boolean needToSplitCriticalEdges = false; - ListIterator blocksIterator = code.listIterator(); - while (blocksIterator.hasNext()) { - BasicBlock block = blocksIterator.next(); - InstructionListIterator iterator = block.listIterator(code); - while (iterator.hasNext()) { - Instruction instruction = iterator.next(); - assert !instruction.isStringSwitch(); - if (instruction.isIntSwitch()) { - IntSwitch intSwitch = instruction.asIntSwitch(); - if (intSwitch.getKey(intSwitch.numberOfKeys() - 1) == Integer.MAX_VALUE) { - if (intSwitch.numberOfKeys() == 1) { - codeRewriter.rewriteSingleKeySwitchToIf(code, block, iterator, intSwitch); - } else { - IntList newSwitchSequences = new IntArrayList(intSwitch.numberOfKeys() - 1); - for (int i = 0; i < intSwitch.numberOfKeys() - 1; i++) { - newSwitchSequences.add(intSwitch.getKey(i)); - } - IntList outliers = new IntArrayList(1); - outliers.add(Integer.MAX_VALUE); - codeRewriter.convertSwitchToSwitchAndIfs( - code, - blocksIterator, - block, - iterator, - intSwitch, - ImmutableList.of(newSwitchSequences), - outliers); - } - needToSplitCriticalEdges = true; - } - } - } - } - - // Rewriting of switches introduces new branching structure. It relies on critical edges - // being split on the way in but does not maintain this property. We therefore split - // critical edges at exit. - if (needToSplitCriticalEdges) { - code.splitCriticalEdges(); - } - } - // See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug(). public static void workaroundNumberConversionRegisterAllocationBug( IRCode code, InternalOptions options) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index 7607596fc2..435b01ca13 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -19,6 +19,7 @@ import com.android.tools.r8.ir.code.InstructionOrPhi; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.ir.optimize.AssumeRemover; import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner; @@ -250,7 +251,7 @@ public final void processMethodCode( codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( code, method, methodProcessor, methodProcessingContext); // If a method was inlined we may be able to prune additional branches. - codeRewriter.simplifyControlFlow(code); + new BranchSimplifier(appView).simplifyBranches(code); // If a method was inlined we may see more trivial computation/conversion of String. boolean isDebugMode = appView.options().debug || method.getOrComputeReachabilitySensitive(appView); From ad1d89136c9da056107c6cc3f258410d1ae7d00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 8 Jun 2023 10:22:13 +0200 Subject: [PATCH 029/153] Split trivial phis analysis from CodeRewriter Change-Id: Ibdc3e6004b397c45fef24dbbe7ded1b658ffc0f3 --- .../tools/r8/ir/conversion/IRConverter.java | 3 +- .../r8/ir/conversion/IRToDexFinalizer.java | 1 - .../r8/ir/conversion/LensCodeRewriter.java | 4 +- .../passes/TrivialPhiSimplifier.java | 113 ++++++++++++++++++ .../tools/r8/ir/optimize/CodeRewriter.java | 93 -------------- 5 files changed, 117 insertions(+), 97 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialPhiSimplifier.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index a804771560..688e1f422d 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -32,6 +32,7 @@ import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; +import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer; import com.android.tools.r8.ir.optimize.AssertionErrorTwoArgsConstructorRewriter; @@ -539,7 +540,7 @@ Timing optimize( if (options.canHaveArtStringNewInitBug()) { timing.begin("Check for new-init issue"); - CodeRewriter.ensureDirectStringNewToInit(code, appView.dexItemFactory()); + TrivialPhiSimplifier.ensureDirectStringNewToInit(code, appView.dexItemFactory()); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java index b7981b96cf..041f05b598 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java @@ -22,7 +22,6 @@ public class IRToDexFinalizer extends IRFinalizer { private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2; - private final InternalOptions options; public IRToDexFinalizer(AppView appView, DeadCodeRemover deadCodeRemover) { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index 1efe77297f..4c15125fa9 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java @@ -104,7 +104,7 @@ import com.android.tools.r8.ir.code.UnusedArgument; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; -import com.android.tools.r8.ir.optimize.CodeRewriter; +import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier; import com.android.tools.r8.ir.optimize.enums.EnumUnboxer; import com.android.tools.r8.optimize.MemberRebindingAnalysis; import com.android.tools.r8.optimize.argumentpropagation.lenscoderewriter.NullCheckInserter; @@ -231,7 +231,7 @@ private void rewritePartial( if (unusedArgument.outValue().hasPhiUsers()) { // See b/240282988: We can end up in situations where the second round of IR processing // introduce phis for irreducible control flow, we need to resolve them. - CodeRewriter.replaceUnusedArgumentTrivialPhis(unusedArgument); + TrivialPhiSimplifier.replaceUnusedArgumentTrivialPhis(unusedArgument); } } } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialPhiSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialPhiSimplifier.java new file mode 100644 index 0000000000..6c4c66fbea --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialPhiSimplifier.java @@ -0,0 +1,113 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import com.android.tools.r8.algorithms.scc.SCC; +import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InvokeDirect; +import com.android.tools.r8.ir.code.NewInstance; +import com.android.tools.r8.ir.code.Phi; +import com.android.tools.r8.ir.code.UnusedArgument; +import com.android.tools.r8.ir.code.Value; +import com.google.common.collect.Sets; +import java.util.List; +import java.util.Set; + +public class TrivialPhiSimplifier { + + public static void replaceUnusedArgumentTrivialPhis(UnusedArgument unusedArgument) { + replaceTrivialPhis(unusedArgument.outValue()); + for (Phi phiUser : unusedArgument.outValue().uniquePhiUsers()) { + phiUser.removeTrivialPhi(); + } + assert !unusedArgument.outValue().hasPhiUsers(); + } + + public static void ensureDirectStringNewToInit(IRCode code, DexItemFactory dexItemFactory) { + for (Instruction instruction : code.instructions()) { + if (instruction.isInvokeDirect()) { + InvokeDirect invoke = instruction.asInvokeDirect(); + DexMethod method = invoke.getInvokedMethod(); + if (dexItemFactory.isConstructor(method) + && method.holder == dexItemFactory.stringType + && invoke.getReceiver().isPhi()) { + NewInstance newInstance = findNewInstance(invoke.getReceiver().asPhi()); + replaceTrivialPhis(newInstance.outValue()); + if (invoke.getReceiver().isPhi()) { + throw new CompilationError( + "Failed to remove trivial phis between new-instance and "); + } + newInstance.markNoSpilling(); + } + } + } + } + + private static NewInstance findNewInstance(Phi phi) { + Set seen = Sets.newIdentityHashSet(); + Set values = Sets.newIdentityHashSet(); + recursiveAddOperands(phi, seen, values); + if (values.size() != 1) { + throw new CompilationError("Failed to identify unique new-instance for "); + } + Value newInstanceValue = values.iterator().next(); + if (newInstanceValue.definition == null || !newInstanceValue.definition.isNewInstance()) { + throw new CompilationError("Invalid defining value for call to "); + } + return newInstanceValue.definition.asNewInstance(); + } + + private static void recursiveAddOperands(Phi phi, Set seen, Set values) { + for (Value operand : phi.getOperands()) { + if (!operand.isPhi()) { + values.add(operand); + } else { + Phi phiOp = operand.asPhi(); + if (seen.add(phiOp)) { + recursiveAddOperands(phiOp, seen, values); + } + } + } + } + + // We compute the set of strongly connected phis making use of the out value and replace all + // trivial ones by the out value. + // This is a simplified variant of the removeRedundantPhis algorithm in Section 3.2 of: + // http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf + private static void replaceTrivialPhis(Value outValue) { + List> components = new SCC(Value::uniquePhiUsers).computeSCC(outValue); + for (int i = components.size() - 1; i >= 0; i--) { + Set component = components.get(i); + if (component.size() == 1 && component.iterator().next() == outValue) { + continue; + } + Set trivialPhis = Sets.newIdentityHashSet(); + for (Value value : component) { + boolean isTrivial = true; + Phi p = value.asPhi(); + for (Value op : p.getOperands()) { + if (op != outValue && !component.contains(op)) { + isTrivial = false; + break; + } + } + if (isTrivial) { + trivialPhis.add(p); + } + } + for (Phi trivialPhi : trivialPhis) { + for (Value op : trivialPhi.getOperands()) { + op.removePhiUser(trivialPhi); + } + trivialPhi.replaceUsers(outValue); + trivialPhi.getBlock().removePhi(trivialPhi); + } + } + } +} 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 756f75bf3d..5d0abc3a5c 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 @@ -14,9 +14,7 @@ import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; -import com.android.tools.r8.algorithms.scc.SCC; import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; -import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AccessControl; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; @@ -88,7 +86,6 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Switch; import com.android.tools.r8.ir.code.Throw; -import com.android.tools.r8.ir.code.UnusedArgument; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; @@ -2851,34 +2848,6 @@ public void logArgumentTypes(DexEncodedMethod method, IRCode code) { iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, empty))); } - public static void replaceUnusedArgumentTrivialPhis(UnusedArgument unusedArgument) { - replaceTrivialPhis(unusedArgument.outValue()); - for (Phi phiUser : unusedArgument.outValue().uniquePhiUsers()) { - phiUser.removeTrivialPhi(); - } - assert !unusedArgument.outValue().hasPhiUsers(); - } - - public static void ensureDirectStringNewToInit(IRCode code, DexItemFactory dexItemFactory) { - for (Instruction instruction : code.instructions()) { - if (instruction.isInvokeDirect()) { - InvokeDirect invoke = instruction.asInvokeDirect(); - DexMethod method = invoke.getInvokedMethod(); - if (dexItemFactory.isConstructor(method) - && method.holder == dexItemFactory.stringType - && invoke.getReceiver().isPhi()) { - NewInstance newInstance = findNewInstance(invoke.getReceiver().asPhi()); - replaceTrivialPhis(newInstance.outValue()); - if (invoke.getReceiver().isPhi()) { - throw new CompilationError( - "Failed to remove trivial phis between new-instance and "); - } - newInstance.markNoSpilling(); - } - } - } - } - // The javac fix for JDK-8272564 has to be rewritten back to invoke-virtual on Object if the // method with an Object signature is not defined on the interface. See // https://bugs.openjdk.java.net/browse/JDK-8272564 @@ -2903,66 +2872,4 @@ public static void rewriteJdk8272564Fix(IRCode code, ProgramMethod context, AppV } } } - - private static NewInstance findNewInstance(Phi phi) { - Set seen = Sets.newIdentityHashSet(); - Set values = Sets.newIdentityHashSet(); - recursiveAddOperands(phi, seen, values); - if (values.size() != 1) { - throw new CompilationError("Failed to identify unique new-instance for "); - } - Value newInstanceValue = values.iterator().next(); - if (newInstanceValue.definition == null || !newInstanceValue.definition.isNewInstance()) { - throw new CompilationError("Invalid defining value for call to "); - } - return newInstanceValue.definition.asNewInstance(); - } - - private static void recursiveAddOperands(Phi phi, Set seen, Set values) { - for (Value operand : phi.getOperands()) { - if (!operand.isPhi()) { - values.add(operand); - } else { - Phi phiOp = operand.asPhi(); - if (seen.add(phiOp)) { - recursiveAddOperands(phiOp, seen, values); - } - } - } - } - - // We compute the set of strongly connected phis making use of the out value and replace all - // trivial ones by the out value. - // This is a simplified variant of the removeRedundantPhis algorithm in Section 3.2 of: - // http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf - private static void replaceTrivialPhis(Value outValue) { - List> components = new SCC(Value::uniquePhiUsers).computeSCC(outValue); - for (int i = components.size() - 1; i >= 0; i--) { - Set component = components.get(i); - if (component.size() == 1 && component.iterator().next() == outValue) { - continue; - } - Set trivialPhis = Sets.newIdentityHashSet(); - for (Value value : component) { - boolean isTrivial = true; - Phi p = value.asPhi(); - for (Value op : p.getOperands()) { - if (op != outValue && !component.contains(op)) { - isTrivial = false; - break; - } - } - if (isTrivial) { - trivialPhis.add(p); - } - } - for (Phi trivialPhi : trivialPhis) { - for (Value op : trivialPhi.getOperands()) { - op.removePhiUser(trivialPhi); - } - trivialPhi.replaceUsers(outValue); - trivialPhi.getBlock().removePhi(trivialPhi); - } - } - } } From beb267fb7cc80d5ec79d7eaa83ee36e5aad44e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 8 Jun 2023 10:26:58 +0200 Subject: [PATCH 030/153] Split ArrayConstructionSimplifier into a CodeRewriterPass Bug: b/284304606 Change-Id: I4f645ada87e3dff8d069e6d7f4dc570136a0844f --- .../tools/r8/ir/conversion/IRConverter.java | 5 +- .../passes/ArrayConstructionSimplifier.java | 474 ++++++++++++++++ .../tools/r8/ir/optimize/CodeRewriter.java | 529 ++---------------- .../ArrayWithDataLengthRewriteTest.java | 4 +- 4 files changed, 525 insertions(+), 487 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 688e1f422d..5cc5c1c7fa 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -27,6 +27,7 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.ArrayConstructionSimplifier; import com.android.tools.r8.ir.conversion.passes.BinopRewriter; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; @@ -752,9 +753,7 @@ Timing optimize( timing.end(); } commonSubexpressionElimination.run(context, code, timing); - timing.begin("Simplify arrays"); - codeRewriter.simplifyArrayConstruction(code); - timing.end(); + new ArrayConstructionSimplifier(appView).run(context, code, timing); timing.begin("Rewrite move result"); codeRewriter.rewriteMoveResult(code); timing.end(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java new file mode 100644 index 0000000000..bc8f8fbed5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java @@ -0,0 +1,474 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.type.ArrayTypeElement; +import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.code.ArrayPut; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeNewArray; +import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator; +import com.android.tools.r8.ir.code.NewArrayEmpty; +import com.android.tools.r8.ir.code.NewArrayFilledData; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions; +import com.android.tools.r8.utils.SetUtils; +import com.android.tools.r8.utils.WorkList; +import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +/** + * Replace new-array followed by stores of constants to all entries with new-array and + * fill-array-data / filled-new-array. + * + *

The format of the new-array and its puts must be of the form: + * + *

+ *   v0 <- new-array T vSize
+ *   ...
+ *   array-put v0 vValue1 vIndex1
+ *   ...
+ *   array-put v0 vValueN vIndexN
+ * 
+ * + *

The flow between the array v0 and its puts must be linear with no other uses of v0 besides the + * array-put instructions, thus any no intermediate instruction (... above) must use v0 and also + * cannot have catch handlers that would transfer out control (those could then have uses of v0). + * + *

The allocation of the new-array can itself have catch handlers, in which case, those are to + * remain active on the translated code. Translated code can have two forms. + * + *

The first is using the original array allocation and filling in its data if it can be encoded: + * + *

+ *   v0 <- new-array T vSize
+ *   filled-array-data v0
+ *   ...
+ *   ...
+ * 
+ * + * The data payload is encoded directly in the instruction so no dependencies are needed for filling + * the data array. Thus, the fill is inserted at the point of the allocation. If the allocation has + * catch handlers its block must be split and the handlers put on the fill instruction too. This is + * correct only because there are no exceptional transfers in (...) that could observe the early + * initialization of the data. + * + *

The second is using filled-new-array and has the form: + * + *

+ * ...
+ * ...
+ * v0 <- filled-new-array T vValue1 ... vValueN
+ * 
+ * + * Here the correctness relies on no exceptional transfers in (...) that could observe the missing + * allocation of the array. The late allocation ensures that the values are available at allocation + * time. If the original allocation has catch handlers then the new allocation needs to link those + * too. In general that may require splitting the block twice so that the new allocation is the + * single throwing instruction in its block. + */ +public class ArrayConstructionSimplifier extends CodeRewriterPass { + + private final DexItemFactory dexItemFactory; + + public ArrayConstructionSimplifier(AppView appView) { + super(appView); + this.dexItemFactory = appView.dexItemFactory(); + } + + @Override + String getTimingId() { + return "ArrayConstructionSimplifier"; + } + + @Override + void rewriteCode(ProgramMethod method, IRCode code) { + WorkList worklist = WorkList.newIdentityWorkList(code.blocks); + while (worklist.hasNext()) { + BasicBlock block = worklist.next(); + simplifyArrayConstructionBlock(block, worklist, code, appView.options()); + } + } + + @Override + boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + return appView.options().isGeneratingDex(); + } + + private void simplifyArrayConstructionBlock( + BasicBlock block, WorkList worklist, IRCode code, InternalOptions options) { + RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions(); + InstructionListIterator it = block.listIterator(code); + while (it.hasNext()) { + FilledArrayCandidate candidate = computeFilledArrayCandidate(it.next(), rewriteOptions); + if (candidate == null) { + continue; + } + FilledArrayConversionInfo info = + computeConversionInfo( + candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex())); + if (info == null) { + continue; + } + + Instruction instructionAfterCandidate = it.peekNext(); + NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty; + DexType arrayType = newArrayEmpty.type; + int size = candidate.size; + Set instructionsToRemove = SetUtils.newIdentityHashSet(size + 1); + if (candidate.useFilledNewArray()) { + assert newArrayEmpty.getLocalInfo() == null; + Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious(); + Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null); + InvokeNewArray invoke = + new InvokeNewArray(arrayType, invokeValue, Arrays.asList(info.values)); + invoke.setPosition(lastArrayPut.getPosition()); + for (Value value : newArrayEmpty.inValues()) { + value.removeUser(newArrayEmpty); + } + newArrayEmpty.outValue().replaceUsers(invokeValue); + instructionsToRemove.add(newArrayEmpty); + + boolean originalAllocationPointHasHandlers = block.hasCatchHandlers(); + boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers(); + + if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) { + info.lastArrayPutIterator.add(invoke); + } else { + BasicBlock insertionBlock = info.lastArrayPutIterator.split(code); + if (originalAllocationPointHasHandlers) { + if (!insertionBlock.isTrivialGoto()) { + BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code); + assert insertionBlock.isTrivialGoto(); + worklist.addIfNotSeen(blockAfterInsertion); + } + insertionBlock.moveCatchHandlers(block); + } else { + worklist.addIfNotSeen(insertionBlock); + } + insertionBlock.listIterator(code).add(invoke); + } + } else { + assert candidate.useFilledArrayData(); + int elementSize = arrayType.elementSizeForPrimitiveArrayType(); + short[] contents = computeArrayFilledData(info.values, size, elementSize); + if (contents == null) { + continue; + } + // fill-array-data requires the new-array-empty instruction to remain, as it does not + // itself create an array. + NewArrayFilledData fillArray = + new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, size, contents); + fillArray.setPosition(newArrayEmpty.getPosition()); + BasicBlock newBlock = + it.addThrowingInstructionToPossiblyThrowingBlock(code, null, fillArray, options); + if (newBlock != null) { + worklist.addIfNotSeen(newBlock); + } + } + + instructionsToRemove.addAll(info.arrayPutsToRemove); + Set visitedBlocks = Sets.newIdentityHashSet(); + for (Instruction instruction : instructionsToRemove) { + BasicBlock ownerBlock = instruction.getBlock(); + // If owner block is null, then the instruction has been removed already. We can't rely on + // just having the block pointer nulled, so the visited blocks guards reprocessing. + if (ownerBlock != null && visitedBlocks.add(ownerBlock)) { + InstructionListIterator removeIt = ownerBlock.listIterator(code); + while (removeIt.hasNext()) { + if (instructionsToRemove.contains(removeIt.next())) { + removeIt.removeOrReplaceByDebugLocalRead(); + } + } + } + } + + // The above has invalidated the block iterator so reset it and continue. + it = block.listIterator(code, instructionAfterCandidate); + } + } + + private short[] computeArrayFilledData(Value[] values, int size, int elementSize) { + for (Value v : values) { + if (!v.isConstant()) { + return null; + } + } + if (elementSize == 1) { + short[] result = new short[(size + 1) / 2]; + for (int i = 0; i < size; i += 2) { + short value = + (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF); + if (i + 1 < size) { + value |= + (short) + ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8); + } + result[i / 2] = value; + } + return result; + } + assert elementSize == 2 || elementSize == 4 || elementSize == 8; + int shortsPerConstant = elementSize / 2; + short[] result = new short[size * shortsPerConstant]; + for (int i = 0; i < size; i++) { + long value = values[i].getConstInstruction().asConstNumber().getRawValue(); + for (int part = 0; part < shortsPerConstant; part++) { + result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL); + } + } + return result; + } + + private static class FilledArrayConversionInfo { + + Value[] values; + List arrayPutsToRemove; + LinearFlowInstructionListIterator lastArrayPutIterator; + + public FilledArrayConversionInfo(int size) { + values = new Value[size]; + arrayPutsToRemove = new ArrayList<>(size); + } + } + + private FilledArrayConversionInfo computeConversionInfo( + FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) { + NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty; + assert it.peekPrevious() == newArrayEmpty; + Value arrayValue = newArrayEmpty.outValue(); + int size = candidate.size; + + // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify + // if types require a cast. + // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g. + // if aput-object throws a ClassCastException if given an object that does not implement the + // desired interface, then we could add check-cast instructions for arguments we're not sure + // about. + DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory); + boolean needsTypeCheck = + !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType; + + FilledArrayConversionInfo info = new FilledArrayConversionInfo(size); + Value[] values = info.values; + int remaining = size; + Set users = newArrayEmpty.outValue().uniqueUsers(); + while (it.hasNext()) { + Instruction instruction = it.next(); + BasicBlock block = instruction.getBlock(); + // If we encounter an instruction that can throw an exception we need to bail out of the + // optimization so that we do not transform half-initialized arrays into fully initialized + // arrays on exceptional edges. If the block has no handlers it is not observable so + // we perform the rewriting. + if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) { + return null; + } + if (!users.contains(instruction)) { + // If any instruction can transfer control between the new-array and the last array put + // then it is not safe to move the new array to the point of the last put. + if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) { + return null; + } + continue; + } + ArrayPut arrayPut = instruction.asArrayPut(); + // If the initialization sequence is broken by another use we cannot use a fill-array-data + // instruction. + if (arrayPut == null || arrayPut.array() != arrayValue) { + return null; + } + if (!arrayPut.index().isConstNumber()) { + return null; + } + if (arrayPut.instructionInstanceCanThrow()) { + assert false; + return null; + } + int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue(); + if (index < 0 || index >= values.length) { + return null; + } + if (values[index] != null) { + return null; + } + Value value = arrayPut.value(); + if (needsTypeCheck && !value.isAlwaysNull(appView)) { + DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory); + if (elementType.isArrayType()) { + if (elementType != valueDexType) { + return null; + } + } else if (valueDexType.isArrayType()) { + // isSubtype asserts for this case. + return null; + } else if (valueDexType.isNullValueType()) { + // Assume instructions can cause value.isAlwaysNull() == false while the DexType is null. + // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest + // that hits this case. + } else { + // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for + // library types (which this helper does not do). + if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) { + return null; + } + } + } + info.arrayPutsToRemove.add(arrayPut); + values[index] = value; + --remaining; + if (remaining == 0) { + info.lastArrayPutIterator = it; + return info; + } + } + return null; + } + + private static class FilledArrayCandidate { + + final NewArrayEmpty newArrayEmpty; + final int size; + final boolean encodeAsFilledNewArray; + + public FilledArrayCandidate( + NewArrayEmpty newArrayEmpty, int size, boolean encodeAsFilledNewArray) { + assert size > 0; + this.newArrayEmpty = newArrayEmpty; + this.size = size; + this.encodeAsFilledNewArray = encodeAsFilledNewArray; + } + + public boolean useFilledNewArray() { + return encodeAsFilledNewArray; + } + + public boolean useFilledArrayData() { + return !useFilledNewArray(); + } + } + + private FilledArrayCandidate computeFilledArrayCandidate( + Instruction instruction, RewriteArrayOptions options) { + NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty(); + if (newArrayEmpty == null) { + return null; + } + if (instruction.getLocalInfo() != null) { + return null; + } + if (!newArrayEmpty.size().isConstant()) { + return null; + } + int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue(); + if (!options.isPotentialSize(size)) { + return null; + } + DexType arrayType = newArrayEmpty.type; + boolean encodeAsFilledNewArray = canUseFilledNewArray(arrayType, size, options); + if (!encodeAsFilledNewArray && !canUseFilledArrayData(arrayType, size, options)) { + return null; + } + // Check that all arguments to the array is the array type or that the array is type Object[]. + if (!options.canUseSubTypesInFilledNewArray() + && arrayType != dexItemFactory.objectArrayType + && !arrayType.isPrimitiveArrayType()) { + DexType elementType = arrayType.toArrayElementType(dexItemFactory); + for (Instruction uniqueUser : newArrayEmpty.outValue().uniqueUsers()) { + if (uniqueUser.isArrayPut() + && uniqueUser.asArrayPut().array() == newArrayEmpty.outValue() + && !checkTypeOfArrayPut(uniqueUser.asArrayPut(), elementType)) { + return null; + } + } + } + return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray); + } + + private boolean checkTypeOfArrayPut(ArrayPut arrayPut, DexType elementType) { + TypeElement valueType = arrayPut.value().getType(); + if (!valueType.isPrimitiveType() && elementType == dexItemFactory.objectType) { + return true; + } + if (valueType.isNullType() && !elementType.isPrimitiveType()) { + return true; + } + if (elementType.isArrayType()) { + if (valueType.isNullType()) { + return true; + } + ArrayTypeElement arrayTypeElement = valueType.asArrayType(); + if (arrayTypeElement == null + || arrayTypeElement.getNesting() != elementType.getNumberOfLeadingSquareBrackets()) { + return false; + } + valueType = arrayTypeElement.getBaseType(); + elementType = elementType.toBaseType(dexItemFactory); + } + assert !valueType.isArrayType(); + assert !elementType.isArrayType(); + if (valueType.isPrimitiveType() && !elementType.isPrimitiveType()) { + return false; + } + if (valueType.isPrimitiveType()) { + return true; + } + DexClass clazz = appView.definitionFor(elementType); + if (clazz == null) { + return false; + } + return clazz.isInterface() || valueType.isClassType(elementType); + } + + private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) { + if (size < options.minSizeForFilledNewArray) { + return false; + } + // filled-new-array is implemented only for int[] and Object[]. + // For int[], using filled-new-array is usually smaller than filled-array-data. + // filled-new-array supports up to 5 registers before it's filled-new-array/range. + if (!arrayType.isPrimitiveArrayType()) { + if (size > options.maxSizeForFilledNewArrayOfReferences) { + return false; + } + if (arrayType == dexItemFactory.stringArrayType) { + return options.canUseFilledNewArrayOfStrings(); + } + if (!options.canUseFilledNewArrayOfNonStringObjects()) { + return false; + } + if (!options.canUseFilledNewArrayOfArrays() + && arrayType.getNumberOfLeadingSquareBrackets() > 1) { + return false; + } + return true; + } + if (arrayType == dexItemFactory.intArrayType) { + return size <= options.maxSizeForFilledNewArrayOfInts; + } + return false; + } + + private boolean canUseFilledArrayData(DexType arrayType, int size, RewriteArrayOptions options) { + // If there is only one element it is typically smaller to generate the array put + // instruction instead of fill array data. + if (size < options.minSizeForFilledArrayData || size > options.maxSizeForFilledArrayData) { + return false; + } + return arrayType.isPrimitiveArrayType(); + } +} 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 5d0abc3a5c..f931cb22e1 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 @@ -30,7 +30,6 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.analysis.type.ArrayTypeElement; import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; @@ -38,7 +37,6 @@ import com.android.tools.r8.ir.analysis.type.TypeUtils; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.code.ArrayLength; -import com.android.tools.r8.ir.code.ArrayPut; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlockIterator; @@ -72,13 +70,9 @@ import com.android.tools.r8.ir.code.InvokeInterface; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; -import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; -import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator; import com.android.tools.r8.ir.code.Move; -import com.android.tools.r8.ir.code.NewArrayEmpty; -import com.android.tools.r8.ir.code.NewArrayFilledData; import com.android.tools.r8.ir.code.NewInstance; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Position; @@ -93,10 +87,7 @@ import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions; import com.android.tools.r8.utils.LazyBox; -import com.android.tools.r8.utils.SetUtils; -import com.android.tools.r8.utils.WorkList; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; @@ -118,7 +109,6 @@ import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.ArrayList; -import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.HashSet; @@ -1141,6 +1131,53 @@ public void splitRangeInvokeConstants(IRCode code) { assert code.isConsistentSSA(appView); } + public void rewriteKnownArrayLengthCalls(IRCode code) { + InstructionListIterator iterator = code.instructionListIterator(); + while (iterator.hasNext()) { + Instruction current = iterator.next(); + if (!current.isArrayLength()) { + continue; + } + + ArrayLength arrayLength = current.asArrayLength(); + if (arrayLength.hasOutValue() && arrayLength.outValue().hasLocalInfo()) { + continue; + } + + Value array = arrayLength.array().getAliasedValue(); + if (array.isPhi() || !arrayLength.array().isNeverNull() || array.hasLocalInfo()) { + continue; + } + + AbstractValue abstractValue = array.getAbstractValue(appView, code.context()); + if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) { + continue; + } + Instruction arrayDefinition = array.getDefinition(); + assert arrayDefinition != null; + + Set phiUsers = arrayLength.outValue().uniquePhiUsers(); + if (arrayDefinition.isNewArrayEmpty()) { + Value size = arrayDefinition.asNewArrayEmpty().size(); + arrayLength.outValue().replaceUsers(size); + iterator.removeOrReplaceByDebugLocalRead(); + } else if (arrayDefinition.isNewArrayFilledData()) { + long size = arrayDefinition.asNewArrayFilledData().size; + if (size > Integer.MAX_VALUE) { + continue; + } + iterator.replaceCurrentInstructionWithConstInt(code, (int) size); + } else if (abstractValue.hasKnownArrayLength()) { + iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength()); + } else { + continue; + } + + phiUsers.forEach(Phi::removeTrivialPhi); + } + assert code.isConsistentSSA(appView); + } + /** * If an instruction is known to be a binop/lit8 or binop//lit16 instruction, update the * instruction to use its own constant that will be defined just before the instruction. This @@ -1558,431 +1595,6 @@ private void shortenLiveRangesInsideBlock( } } - private short[] computeArrayFilledData(Value[] values, int size, int elementSize) { - for (Value v : values) { - if (!v.isConstant()) { - return null; - } - } - if (elementSize == 1) { - short[] result = new short[(size + 1) / 2]; - for (int i = 0; i < size; i += 2) { - short value = - (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF); - if (i + 1 < size) { - value |= - (short) - ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8); - } - result[i / 2] = value; - } - return result; - } - assert elementSize == 2 || elementSize == 4 || elementSize == 8; - int shortsPerConstant = elementSize / 2; - short[] result = new short[size * shortsPerConstant]; - for (int i = 0; i < size; i++) { - long value = values[i].getConstInstruction().asConstNumber().getRawValue(); - for (int part = 0; part < shortsPerConstant; part++) { - result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL); - } - } - return result; - } - - private static class FilledArrayConversionInfo { - - Value[] values; - List arrayPutsToRemove; - LinearFlowInstructionListIterator lastArrayPutIterator; - - public FilledArrayConversionInfo(int size) { - values = new Value[size]; - arrayPutsToRemove = new ArrayList<>(size); - } - } - - private FilledArrayConversionInfo computeConversionInfo( - FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) { - NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty; - assert it.peekPrevious() == newArrayEmpty; - Value arrayValue = newArrayEmpty.outValue(); - int size = candidate.size; - - // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify - // if types require a cast. - // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g. - // if aput-object throws a ClassCastException if given an object that does not implement the - // desired interface, then we could add check-cast instructions for arguments we're not sure - // about. - DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory); - boolean needsTypeCheck = - !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType; - - FilledArrayConversionInfo info = new FilledArrayConversionInfo(size); - Value[] values = info.values; - int remaining = size; - Set users = newArrayEmpty.outValue().uniqueUsers(); - while (it.hasNext()) { - Instruction instruction = it.next(); - BasicBlock block = instruction.getBlock(); - // If we encounter an instruction that can throw an exception we need to bail out of the - // optimization so that we do not transform half-initialized arrays into fully initialized - // arrays on exceptional edges. If the block has no handlers it is not observable so - // we perform the rewriting. - if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) { - return null; - } - if (!users.contains(instruction)) { - // If any instruction can transfer control between the new-array and the last array put - // then it is not safe to move the new array to the point of the last put. - if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) { - return null; - } - continue; - } - ArrayPut arrayPut = instruction.asArrayPut(); - // If the initialization sequence is broken by another use we cannot use a fill-array-data - // instruction. - if (arrayPut == null || arrayPut.array() != arrayValue) { - return null; - } - if (!arrayPut.index().isConstNumber()) { - return null; - } - if (arrayPut.instructionInstanceCanThrow()) { - assert false; - return null; - } - int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue(); - if (index < 0 || index >= values.length) { - return null; - } - if (values[index] != null) { - return null; - } - Value value = arrayPut.value(); - if (needsTypeCheck && !value.isAlwaysNull(appView)) { - DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory); - if (elementType.isArrayType()) { - if (elementType != valueDexType) { - return null; - } - } else if (valueDexType.isArrayType()) { - // isSubtype asserts for this case. - return null; - } else if (valueDexType.isNullValueType()) { - // Assume instructions can cause value.isAlwaysNull() == false while the DexType is null. - // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest - // that hits this case. - } else { - // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for - // library types (which this helper does not do). - if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) { - return null; - } - } - } - info.arrayPutsToRemove.add(arrayPut); - values[index] = value; - --remaining; - if (remaining == 0) { - info.lastArrayPutIterator = it; - return info; - } - } - return null; - } - - private static class FilledArrayCandidate { - - final NewArrayEmpty newArrayEmpty; - final int size; - final boolean encodeAsFilledNewArray; - - public FilledArrayCandidate( - NewArrayEmpty newArrayEmpty, int size, boolean encodeAsFilledNewArray) { - assert size > 0; - this.newArrayEmpty = newArrayEmpty; - this.size = size; - this.encodeAsFilledNewArray = encodeAsFilledNewArray; - } - - public boolean useFilledNewArray() { - return encodeAsFilledNewArray; - } - - public boolean useFilledArrayData() { - return !useFilledNewArray(); - } - } - - private FilledArrayCandidate computeFilledArrayCandidate( - Instruction instruction, RewriteArrayOptions options) { - NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty(); - if (newArrayEmpty == null) { - return null; - } - if (instruction.getLocalInfo() != null) { - return null; - } - if (!newArrayEmpty.size().isConstant()) { - return null; - } - int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue(); - if (!options.isPotentialSize(size)) { - return null; - } - DexType arrayType = newArrayEmpty.type; - boolean encodeAsFilledNewArray = canUseFilledNewArray(arrayType, size, options); - if (!encodeAsFilledNewArray && !canUseFilledArrayData(arrayType, size, options)) { - return null; - } - // Check that all arguments to the array is the array type or that the array is type Object[]. - if (!options.canUseSubTypesInFilledNewArray() - && arrayType != dexItemFactory.objectArrayType - && !arrayType.isPrimitiveArrayType()) { - DexType elementType = arrayType.toArrayElementType(dexItemFactory); - for (Instruction uniqueUser : newArrayEmpty.outValue().uniqueUsers()) { - if (uniqueUser.isArrayPut() - && uniqueUser.asArrayPut().array() == newArrayEmpty.outValue() - && !checkTypeOfArrayPut(uniqueUser.asArrayPut(), elementType)) { - return null; - } - } - } - return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray); - } - - private boolean checkTypeOfArrayPut(ArrayPut arrayPut, DexType elementType) { - TypeElement valueType = arrayPut.value().getType(); - if (!valueType.isPrimitiveType() && elementType == dexItemFactory.objectType) { - return true; - } - if (valueType.isNullType() && !elementType.isPrimitiveType()) { - return true; - } - if (elementType.isArrayType()) { - if (valueType.isNullType()) { - return true; - } - ArrayTypeElement arrayTypeElement = valueType.asArrayType(); - if (arrayTypeElement == null - || arrayTypeElement.getNesting() != elementType.getNumberOfLeadingSquareBrackets()) { - return false; - } - valueType = arrayTypeElement.getBaseType(); - elementType = elementType.toBaseType(dexItemFactory); - } - assert !valueType.isArrayType(); - assert !elementType.isArrayType(); - if (valueType.isPrimitiveType() && !elementType.isPrimitiveType()) { - return false; - } - if (valueType.isPrimitiveType()) { - return true; - } - DexClass clazz = appView.definitionFor(elementType); - if (clazz == null) { - return false; - } - return clazz.isInterface() || valueType.isClassType(elementType); - } - - private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) { - if (size < options.minSizeForFilledNewArray) { - return false; - } - // filled-new-array is implemented only for int[] and Object[]. - // For int[], using filled-new-array is usually smaller than filled-array-data. - // filled-new-array supports up to 5 registers before it's filled-new-array/range. - if (!arrayType.isPrimitiveArrayType()) { - if (size > options.maxSizeForFilledNewArrayOfReferences) { - return false; - } - if (arrayType == dexItemFactory.stringArrayType) { - return options.canUseFilledNewArrayOfStrings(); - } - if (!options.canUseFilledNewArrayOfNonStringObjects()) { - return false; - } - if (!options.canUseFilledNewArrayOfArrays() - && arrayType.getNumberOfLeadingSquareBrackets() > 1) { - return false; - } - return true; - } - if (arrayType == dexItemFactory.intArrayType) { - return size <= options.maxSizeForFilledNewArrayOfInts; - } - return false; - } - - private boolean canUseFilledArrayData(DexType arrayType, int size, RewriteArrayOptions options) { - // If there is only one element it is typically smaller to generate the array put - // instruction instead of fill array data. - if (size < options.minSizeForFilledArrayData || size > options.maxSizeForFilledArrayData) { - return false; - } - return arrayType.isPrimitiveArrayType(); - } - - /** - * Replace new-array followed by stores of constants to all entries with new-array and - * fill-array-data / filled-new-array. - * - *

The format of the new-array and its puts must be of the form: - * - *

-   *   v0 <- new-array T vSize
-   *   ...
-   *   array-put v0 vValue1 vIndex1
-   *   ...
-   *   array-put v0 vValueN vIndexN
-   * 
- * - *

The flow between the array v0 and its puts must be linear with no other uses of v0 besides - * the array-put instructions, thus any no intermediate instruction (... above) must use v0 and - * also cannot have catch handlers that would transfer out control (those could then have uses of - * v0). - * - *

The allocation of the new-array can itself have catch handlers, in which case, those are to - * remain active on the translated code. Translated code can have two forms. - * - *

The first is using the original array allocation and filling in its data if it can be - * encoded: - * - *

-   *   v0 <- new-array T vSize
-   *   filled-array-data v0
-   *   ...
-   *   ...
-   * 
- * - * The data payload is encoded directly in the instruction so no dependencies are needed for - * filling the data array. Thus, the fill is inserted at the point of the allocation. If the - * allocation has catch handlers its block must be split and the handlers put on the fill - * instruction too. This is correct only because there are no exceptional transfers in (...) that - * could observe the early initialization of the data. - * - *

The second is using filled-new-array and has the form: - * - *

-   * ...
-   * ...
-   * v0 <- filled-new-array T vValue1 ... vValueN
-   * 
- * - * Here the correctness relies on no exceptional transfers in (...) that could observe the missing - * allocation of the array. The late allocation ensures that the values are available at - * allocation time. If the original allocation has catch handlers then the new allocation needs to - * link those too. In general that may require splitting the block twice so that the new - * allocation is the single throwing instruction in its block. - */ - public void simplifyArrayConstruction(IRCode code) { - if (options.isGeneratingClassFiles()) { - return; - } - WorkList worklist = WorkList.newIdentityWorkList(code.blocks); - while (worklist.hasNext()) { - BasicBlock block = worklist.next(); - simplifyArrayConstructionBlock(block, worklist, code, options); - } - } - - private void simplifyArrayConstructionBlock( - BasicBlock block, WorkList worklist, IRCode code, InternalOptions options) { - RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions(); - InstructionListIterator it = block.listIterator(code); - while (it.hasNext()) { - FilledArrayCandidate candidate = computeFilledArrayCandidate(it.next(), rewriteOptions); - if (candidate == null) { - continue; - } - FilledArrayConversionInfo info = - computeConversionInfo( - candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex())); - if (info == null) { - continue; - } - - Instruction instructionAfterCandidate = it.peekNext(); - NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty; - DexType arrayType = newArrayEmpty.type; - int size = candidate.size; - Set instructionsToRemove = SetUtils.newIdentityHashSet(size + 1); - if (candidate.useFilledNewArray()) { - assert newArrayEmpty.getLocalInfo() == null; - Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious(); - Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null); - InvokeNewArray invoke = - new InvokeNewArray(arrayType, invokeValue, Arrays.asList(info.values)); - invoke.setPosition(lastArrayPut.getPosition()); - for (Value value : newArrayEmpty.inValues()) { - value.removeUser(newArrayEmpty); - } - newArrayEmpty.outValue().replaceUsers(invokeValue); - instructionsToRemove.add(newArrayEmpty); - - boolean originalAllocationPointHasHandlers = block.hasCatchHandlers(); - boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers(); - - if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) { - info.lastArrayPutIterator.add(invoke); - } else { - BasicBlock insertionBlock = info.lastArrayPutIterator.split(code); - if (originalAllocationPointHasHandlers) { - if (!insertionBlock.isTrivialGoto()) { - BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code); - assert insertionBlock.isTrivialGoto(); - worklist.addIfNotSeen(blockAfterInsertion); - } - insertionBlock.moveCatchHandlers(block); - } else { - worklist.addIfNotSeen(insertionBlock); - } - insertionBlock.listIterator(code).add(invoke); - } - } else { - assert candidate.useFilledArrayData(); - int elementSize = arrayType.elementSizeForPrimitiveArrayType(); - short[] contents = computeArrayFilledData(info.values, size, elementSize); - if (contents == null) { - continue; - } - // fill-array-data requires the new-array-empty instruction to remain, as it does not - // itself create an array. - NewArrayFilledData fillArray = - new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, size, contents); - fillArray.setPosition(newArrayEmpty.getPosition()); - BasicBlock newBlock = - it.addThrowingInstructionToPossiblyThrowingBlock(code, null, fillArray, options); - if (newBlock != null) { - worklist.addIfNotSeen(newBlock); - } - } - - instructionsToRemove.addAll(info.arrayPutsToRemove); - Set visitedBlocks = Sets.newIdentityHashSet(); - for (Instruction instruction : instructionsToRemove) { - BasicBlock ownerBlock = instruction.getBlock(); - // If owner block is null, then the instruction has been removed already. We can't rely on - // just having the block pointer nulled, so the visited blocks guards reprocessing. - if (ownerBlock != null && visitedBlocks.add(ownerBlock)) { - InstructionListIterator removeIt = ownerBlock.listIterator(code); - while (removeIt.hasNext()) { - if (instructionsToRemove.contains(removeIt.next())) { - removeIt.removeOrReplaceByDebugLocalRead(); - } - } - } - } - - // The above has invalidated the block iterator so reset it and continue. - it = block.listIterator(code, instructionAfterCandidate); - } - } - // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block. private static boolean hasLocalOrLineChangeBetween( Instruction from, Instruction to, DexString localVar) { @@ -2552,53 +2164,6 @@ private void insertNotNullCheck( assert block.exit().asGoto().getTarget() == target; } - public void rewriteKnownArrayLengthCalls(IRCode code) { - InstructionListIterator iterator = code.instructionListIterator(); - while (iterator.hasNext()) { - Instruction current = iterator.next(); - if (!current.isArrayLength()) { - continue; - } - - ArrayLength arrayLength = current.asArrayLength(); - if (arrayLength.hasOutValue() && arrayLength.outValue().hasLocalInfo()) { - continue; - } - - Value array = arrayLength.array().getAliasedValue(); - if (array.isPhi() || !arrayLength.array().isNeverNull() || array.hasLocalInfo()) { - continue; - } - - AbstractValue abstractValue = array.getAbstractValue(appView, code.context()); - if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) { - continue; - } - Instruction arrayDefinition = array.getDefinition(); - assert arrayDefinition != null; - - Set phiUsers = arrayLength.outValue().uniquePhiUsers(); - if (arrayDefinition.isNewArrayEmpty()) { - Value size = arrayDefinition.asNewArrayEmpty().size(); - arrayLength.outValue().replaceUsers(size); - iterator.removeOrReplaceByDebugLocalRead(); - } else if (arrayDefinition.isNewArrayFilledData()) { - long size = arrayDefinition.asNewArrayFilledData().size; - if (size > Integer.MAX_VALUE) { - continue; - } - iterator.replaceCurrentInstructionWithConstInt(code, (int) size); - } else if (abstractValue.hasKnownArrayLength()) { - iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength()); - } else { - continue; - } - - phiUsers.forEach(Phi::removeTrivialPhi); - } - assert code.isConsistentSSA(appView); - } - /** * Remove moves that are not actually used by instructions in exiting paths. These moves can arise * due to debug local info needing a particular value and the live-interval for it then moves it diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java index bcacbe7e53..a09de37aa2 100644 --- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java +++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java @@ -13,7 +13,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.optimize.CodeRewriter; +import com.android.tools.r8.ir.conversion.passes.ArrayConstructionSimplifier; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; @@ -64,7 +64,7 @@ public void r8() throws Exception { } private void transformArray(IRCode irCode, AppView appView) { - new CodeRewriter(appView).simplifyArrayConstruction(irCode); + new ArrayConstructionSimplifier(appView).run(irCode.context(), irCode); String name = irCode.context().getReference().getName().toString(); if (name.contains("filledArrayData")) { assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData)); From c7ccd58a55db8072ff42cb631c9e312f7c1f986d Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Thu, 8 Jun 2023 09:15:03 +0200 Subject: [PATCH 031/153] [Compose] Update ComposeSourceFileTest to test existing classes Bug: b/286023274 Change-Id: Ic912fb1289f46a536983f40b9b6cd09581e5792b --- .../mappingcompose/ComposeSourceFileTest.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java index b07c55c202..d155375951 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java @@ -31,22 +31,34 @@ public static TestParametersCollection data() { private static final String mappingFoo = StringUtils.unixLines( - "# {'id':'com.android.tools.r8.mapping','version':'experimental'}", - "com.foo -> a:", - " # {'id':'sourceFile','fileName':'Foo.kt'}"); + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "com.foo -> A:", + " # {'id':'sourceFile','fileName':'Foo.kt'}", + "com.bar -> B:", + " # {'id':'sourceFile','fileName':'Bar.kt'}", + "com.baz -> C:"); private static final String mappingBar = StringUtils.unixLines( - "# {'id':'com.android.tools.r8.mapping','version':'experimental'}", - "com.bar -> c:", - " # {'id':'sourceFile','fileName':'Bar.kt'}", - "a -> b:"); + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "A -> a:", + " # {'id':'sourceFile','fileName':'some-hash-inserted-into-source-file'}", + "B -> b:", + "C -> c:", + " # {'id':'sourceFile','fileName':'some-other-hash-inserted-into-source-file'}", + "com.qux -> d:", + " # {'id':'sourceFile','fileName':'Qux.kt'}"); private static final String mappingResult = StringUtils.unixLines( - "# {'id':'com.android.tools.r8.mapping','version':'experimental'}", - "com.bar -> c:", + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "com.bar -> b:", "# {'id':'sourceFile','fileName':'Bar.kt'}", - "com.foo -> b:", - "# {'id':'sourceFile','fileName':'Foo.kt'}"); + "com.baz -> c:", + // TODO(b/286023274): We should not insert 'sourceFile' on composed classes. + "# {'id':'sourceFile','fileName':'some-other-hash-inserted-into-source-file'}", + "com.foo -> a:", + "# {'id':'sourceFile','fileName':'Foo.kt'}", + "com.qux -> d:", + "# {'id':'sourceFile','fileName':'Qux.kt'}"); @Test public void testCompose() throws Exception { From 236c86beff59e99c5961ca28bc96b18ec1121561 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Thu, 8 Jun 2023 09:23:39 +0200 Subject: [PATCH 032/153] [Compose] Do not compose new file name information for existing class Bug: b/286023274 Change-Id: I352a1617cfce6cdda11a51b0fd00429517ea2c52 --- src/main/java/com/android/tools/r8/naming/ComposingBuilder.java | 2 +- .../android/tools/r8/mappingcompose/ComposeSourceFileTest.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index d87e0360bf..502f1203d2 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -1306,7 +1306,7 @@ private void composeMappingInformation( } newMappingInformation.forEach( info -> { - if (!nonCompasableNewInfos.contains(info)) { + if (!nonCompasableNewInfos.contains(info) && !info.isFileNameInformation()) { consumer.accept(info); } }); diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java index d155375951..434c494076 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java @@ -53,8 +53,6 @@ public static TestParametersCollection data() { "com.bar -> b:", "# {'id':'sourceFile','fileName':'Bar.kt'}", "com.baz -> c:", - // TODO(b/286023274): We should not insert 'sourceFile' on composed classes. - "# {'id':'sourceFile','fileName':'some-other-hash-inserted-into-source-file'}", "com.foo -> a:", "# {'id':'sourceFile','fileName':'Foo.kt'}", "com.qux -> d:", From 3d2d5fe7033d74a488e6d21847f4c2586a0c14f0 Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Thu, 8 Jun 2023 08:47:24 +0200 Subject: [PATCH 033/153] Add getter for increment on Inc instruction Fixed: b/286146481 Change-Id: Ic97d096f9d36622cc64737923d8cadf05f53bf82 --- src/main/java/com/android/tools/r8/ir/code/Inc.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/android/tools/r8/ir/code/Inc.java b/src/main/java/com/android/tools/r8/ir/code/Inc.java index 72b2bf2d1a..5d3a877bd9 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Inc.java +++ b/src/main/java/com/android/tools/r8/ir/code/Inc.java @@ -33,6 +33,10 @@ public int opcode() { return Opcodes.INC; } + public int getIncrement() { + return increment; + } + @Override public T accept(InstructionVisitor visitor) { return visitor.visit(this); From 230779d84a00045458d2574a834273250e00928e Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 8 Jun 2023 09:41:34 +0200 Subject: [PATCH 034/153] Get number of threads from ExecutorService in Timing Change-Id: I6c72e74cd93763eb089cd32d2ed082d68f5e6c04 --- .../java/com/android/tools/r8/dex/ApplicationWriter.java | 6 ++---- .../android/tools/r8/dex/ApplicationWriterExperimental.java | 3 +-- .../android/tools/r8/ir/conversion/PostMethodProcessor.java | 3 +-- .../tools/r8/ir/conversion/PrimaryMethodProcessor.java | 3 +-- src/main/java/com/android/tools/r8/utils/Timing.java | 5 +++++ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index 073cc8db1a..3d5f3878b5 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java @@ -325,8 +325,7 @@ protected void writeVirtualFiles( List forcedStrings, Timing timing) throws ExecutionException { - TimingMerger merger = - timing.beginMerger("Write files", ThreadUtils.getNumberOfThreads(executorService)); + TimingMerger merger = timing.beginMerger("Write files", executorService); Collection timings = ThreadUtils.processItemsWithResults( virtualFiles, @@ -395,8 +394,7 @@ public void write(ExecutorService executorService, AndroidApp inputApp) { // Compute offsets and rewrite jumbo strings so that code offsets are fixed. - TimingMerger merger = - timing.beginMerger("Pre-write phase", ThreadUtils.getNumberOfThreads(executorService)); + TimingMerger merger = timing.beginMerger("Pre-write phase", executorService); Collection timings = rewriteJumboStringsAndComputeDebugRepresentation( executorService, virtualFiles, lazyDexStrings); diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java index 853d01e9d5..8324c910a3 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java @@ -96,8 +96,7 @@ protected void writeVirtualFiles( List virtualFiles, List forcedStrings, Timing timing) { - TimingMerger merger = - timing.beginMerger("Write files", ThreadUtils.getNumberOfThreads(executorService)); + TimingMerger merger = timing.beginMerger("Write files", executorService); Collection timings; // TODO(b/249922554): Current limitations for the experimental flag. assert globalsSyntheticsConsumer == null; diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java index fe3085adf8..ab073ddccb 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java @@ -178,8 +178,7 @@ void forEachMethod( ExecutorService executorService, Timing timing) throws ExecutionException { - TimingMerger merger = - timing.beginMerger("secondary-processor", ThreadUtils.getNumberOfThreads(executorService)); + TimingMerger merger = timing.beginMerger("secondary-processor", executorService); while (!waves.isEmpty()) { wave = waves.removeFirst(); assert !wave.isEmpty(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java index 9da34d4e24..45450f97be 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java @@ -129,8 +129,7 @@ void forEachMethod( Timing timing, ExecutorService executorService) throws ExecutionException { - TimingMerger merger = - timing.beginMerger("primary-processor", ThreadUtils.getNumberOfThreads(executorService)); + TimingMerger merger = timing.beginMerger("primary-processor", executorService); while (!waves.isEmpty()) { processorContext = appView.createProcessorContext(); wave = waves.removeFirst(); diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java index 1e61b88fe2..143bc2fc74 100644 --- a/src/main/java/com/android/tools/r8/utils/Timing.java +++ b/src/main/java/com/android/tools/r8/utils/Timing.java @@ -19,6 +19,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; public class Timing { @@ -373,6 +374,10 @@ public void end() { } } + public final TimingMerger beginMerger(String title, ExecutorService executorService) { + return beginMerger(title, ThreadUtils.getNumberOfThreads(executorService)); + } + public TimingMerger beginMerger(String title, int numberOfThreads) { return new TimingMerger(title, numberOfThreads, this); } From 2c5ec4f9902b0e5ae8e62fcdb93117caab9370ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 8 Jun 2023 10:28:59 +0200 Subject: [PATCH 035/153] Split TrivialGotosSimplifier into a codeRewriterPass Bug: b/284304606 Change-Id: I816595b609937f1de97f87f1a297a540f9691ddd --- .../tools/r8/ir/conversion/CfBuilder.java | 7 +- .../tools/r8/ir/conversion/DexBuilder.java | 4 +- .../r8/ir/conversion/IRToDexFinalizer.java | 6 +- .../passes/TrivialGotosCollapser.java | 204 ++++++++++++++++++ .../tools/r8/ir/optimize/CodeRewriter.java | 170 --------------- .../optimize/TrivialGotoEliminationTest.java | 5 +- 6 files changed, 217 insertions(+), 179 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java index 81f5c147d6..16fa00cb6c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java @@ -52,7 +52,7 @@ import com.android.tools.r8.ir.code.UninitializedThisLocalRead; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.Xor; -import com.android.tools.r8.ir.optimize.CodeRewriter; +import com.android.tools.r8.ir.conversion.passes.TrivialGotosCollapser; import com.android.tools.r8.ir.optimize.DeadCodeRemover; import com.android.tools.r8.ir.optimize.PeepholeOptimizer; import com.android.tools.r8.ir.optimize.PhiOptimizations; @@ -193,10 +193,11 @@ public CfCode build(DeadCodeRemover deadCodeRemover, Timing timing) { loadStoreHelper.insertPhiMoves(registerAllocator); timing.end(); + TrivialGotosCollapser trivialGotosCollapser = new TrivialGotosCollapser(appView); timing.begin("BasicBlock peephole optimizations"); if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) { for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) { - CodeRewriter.collapseTrivialGotos(appView, code); + trivialGotosCollapser.run(code.context(), code, timing); PeepholeOptimizer.removeIdenticalPredecessorBlocks(code, registerAllocator); PeepholeOptimizer.shareIdenticalBlockSuffix( code, registerAllocator, SUFFIX_SHARING_OVERHEAD); @@ -206,7 +207,7 @@ public CfCode build(DeadCodeRemover deadCodeRemover, Timing timing) { timing.time("Rewrite Iinc patterns", this::rewriteIincPatterns); - CodeRewriter.collapseTrivialGotos(appView, code); + trivialGotosCollapser.run(code.context(), code, timing); timing.begin("Remove redundant debug positions"); DexBuilder.removeRedundantDebugPositions(code); timing.end(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java index cd7b5b9043..1ddfeace47 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java @@ -63,7 +63,7 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Return; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.optimize.CodeRewriter; +import com.android.tools.r8.ir.conversion.passes.TrivialGotosCollapser; import com.android.tools.r8.ir.regalloc.RegisterAllocator; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOutputMode; @@ -401,7 +401,7 @@ private static void removeTrivialGotoBlocks(IRCode code) { } if (allMatch) { currentBlock.removeInstruction(debugPosition); - CodeRewriter.unlinkTrivialGotoBlock(currentBlock, exit.getTarget()); + TrivialGotosCollapser.unlinkTrivialGotoBlock(currentBlock, exit.getTarget()); code.removeBlocks(Collections.singleton(currentBlock)); // Having removed the block at blockIndex, the previous block may now be a trivial // fallthrough from an if/switch. Rewind to that point and retry. This avoids iterating to diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java index 041f05b598..ddcd9aca2e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java @@ -9,6 +9,7 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.conversion.passes.TrivialGotosCollapser; import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring; import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.DeadCodeRemover; @@ -59,17 +60,18 @@ private RegisterAllocator performRegisterAllocation( LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code); registerAllocator.allocateRegisters(); timing.end(); + TrivialGotosCollapser trivialGotosCollapser = new TrivialGotosCollapser(appView); if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) { timing.begin("Peephole optimize"); for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) { - CodeRewriter.collapseTrivialGotos(appView, code); + trivialGotosCollapser.run(code.context(), code, timing); PeepholeOptimizer.optimize(appView, code, registerAllocator); } timing.end(); } timing.begin("Clean up"); CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator); - CodeRewriter.collapseTrivialGotos(appView, code); + trivialGotosCollapser.run(code.context(), code, timing); timing.end(); return registerAllocator; } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java new file mode 100644 index 0000000000..7c9b4d0046 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java @@ -0,0 +1,204 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.If; +import com.android.tools.r8.ir.code.Switch; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +/** + * Rewrite all branch targets to the destination of trivial goto chains when possible. Does not + * rewrite fallthrough targets as that would require block reordering and the transformation only + * makes sense after SSA destruction where there are no phis. + */ +public class TrivialGotosCollapser extends CodeRewriterPass { + + public TrivialGotosCollapser(AppView appView) { + super(appView); + } + + @Override + String getTimingId() { + return "TrivialGotosCollapser"; + } + + @Override + void rewriteCode(ProgramMethod method, IRCode code) { + assert code.isConsistentGraph(appView); + List blocksToRemove = new ArrayList<>(); + // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove + // first round of trivial goto blocks. + ListIterator iterator = code.listIterator(); + assert iterator.hasNext(); + BasicBlock block = iterator.next(); + BasicBlock nextBlock; + + do { + nextBlock = iterator.hasNext() ? iterator.next() : null; + if (block.isTrivialGoto()) { + collapseTrivialGoto(code, block, nextBlock, blocksToRemove); + } + if (block.exit().isIf()) { + collapseIfTrueTarget(block); + } + if (block.exit().isSwitch()) { + collapseNonFallthroughSwitchTargets(block); + } + block = nextBlock; + } while (nextBlock != null); + code.removeBlocks(blocksToRemove); + // Get rid of gotos to the next block. + while (!blocksToRemove.isEmpty()) { + blocksToRemove = new ArrayList<>(); + iterator = code.listIterator(); + block = iterator.next(); + do { + nextBlock = iterator.hasNext() ? iterator.next() : null; + if (block.isTrivialGoto()) { + collapseTrivialGoto(code, block, nextBlock, blocksToRemove); + } + block = nextBlock; + } while (block != null); + code.removeBlocks(blocksToRemove); + } + assert removedTrivialGotos(code); + assert code.isConsistentGraph(appView); + } + + @Override + boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + return true; + } + + public static void unlinkTrivialGotoBlock(BasicBlock block, BasicBlock target) { + assert block.isTrivialGoto(); + for (BasicBlock pred : block.getPredecessors()) { + pred.replaceSuccessor(block, target); + } + for (BasicBlock succ : block.getSuccessors()) { + succ.getMutablePredecessors().remove(block); + } + for (BasicBlock pred : block.getPredecessors()) { + if (!target.getPredecessors().contains(pred)) { + target.getMutablePredecessors().add(pred); + } + } + } + + private boolean isFallthroughBlock(BasicBlock block) { + for (BasicBlock pred : block.getPredecessors()) { + if (pred.exit().fallthroughBlock() == block) { + return true; + } + } + return false; + } + + private boolean removedTrivialGotos(IRCode code) { + ListIterator iterator = code.listIterator(); + assert iterator.hasNext(); + BasicBlock block = iterator.next(); + BasicBlock nextBlock; + do { + nextBlock = iterator.hasNext() ? iterator.next() : null; + // Trivial goto block are only kept if they are self-targeting or are targeted by + // fallthroughs. + BasicBlock blk = block; // Additional local for lambda below. + assert !block.isTrivialGoto() + || block.exit().asGoto().getTarget() == block + || code.entryBlock() == block + || block.getPredecessors().stream().anyMatch((b) -> b.exit().fallthroughBlock() == blk); + // Trivial goto blocks never target the next block (in that case there should just be a + // fallthrough). + assert !block.isTrivialGoto() || block.exit().asGoto().getTarget() != nextBlock; + block = nextBlock; + } while (block != null); + return true; + } + + private void collapseTrivialGoto( + IRCode code, BasicBlock block, BasicBlock nextBlock, List blocksToRemove) { + + // This is the base case for GOTO loops. + if (block.exit().asGoto().getTarget() == block) { + return; + } + + BasicBlock target = block.endOfGotoChain(); + + boolean needed = false; + + if (target == null) { + // This implies we are in a loop of GOTOs. In that case, we will iteratively remove each + // trivial GOTO one-by-one until the above base case (one block targeting itself) is left. + target = block.exit().asGoto().getTarget(); + } + + if (target != nextBlock) { + // Not targeting the fallthrough block, determine if we need this goto. We need it if + // a fallthrough can hit this block. That is the case if the block is the entry block + // or if one of the predecessors fall through to the block. + needed = code.entryBlock() == block || isFallthroughBlock(block); + } + + if (!needed) { + blocksToRemove.add(block); + unlinkTrivialGotoBlock(block, target); + } + } + + private void collapseIfTrueTarget(BasicBlock block) { + If insn = block.exit().asIf(); + BasicBlock target = insn.getTrueTarget(); + BasicBlock newTarget = target.endOfGotoChain(); + BasicBlock fallthrough = insn.fallthroughBlock(); + BasicBlock newFallthrough = fallthrough.endOfGotoChain(); + if (newTarget != null && target != newTarget) { + insn.getBlock().replaceSuccessor(target, newTarget); + target.getMutablePredecessors().remove(block); + if (!newTarget.getPredecessors().contains(block)) { + newTarget.getMutablePredecessors().add(block); + } + } + if (block.exit().isIf()) { + insn = block.exit().asIf(); + if (insn.getTrueTarget() == newFallthrough) { + // Replace if with the same true and fallthrough target with a goto to the fallthrough. + block.replaceSuccessor(insn.getTrueTarget(), fallthrough); + assert block.exit().isGoto(); + assert block.exit().asGoto().getTarget() == fallthrough; + } + } + } + + private void collapseNonFallthroughSwitchTargets(BasicBlock block) { + Switch insn = block.exit().asSwitch(); + BasicBlock fallthroughBlock = insn.fallthroughBlock(); + Set replacedBlocks = new HashSet<>(); + for (int j = 0; j < insn.targetBlockIndices().length; j++) { + BasicBlock target = insn.targetBlock(j); + if (target != fallthroughBlock) { + BasicBlock newTarget = target.endOfGotoChain(); + if (newTarget != null && target != newTarget && !replacedBlocks.contains(target)) { + insn.getBlock().replaceSuccessor(target, newTarget); + target.getMutablePredecessors().remove(block); + if (!newTarget.getPredecessors().contains(block)) { + newTarget.getMutablePredecessors().add(block); + } + replacedBlocks.add(target); + } + } + } + } +} 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 f931cb22e1..719a05aeed 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 @@ -78,7 +78,6 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.SyntheticPosition; import com.android.tools.r8.ir.code.StaticGet; -import com.android.tools.r8.ir.code.Switch; import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; @@ -111,7 +110,6 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; -import java.util.HashSet; import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -201,28 +199,6 @@ public static void removeAssumeInstructions(AppView appView, IRCode code) { assert Streams.stream(code.instructions()).noneMatch(Instruction::isAssume); } - private static boolean removedTrivialGotos(IRCode code) { - ListIterator iterator = code.listIterator(); - assert iterator.hasNext(); - BasicBlock block = iterator.next(); - BasicBlock nextBlock; - do { - nextBlock = iterator.hasNext() ? iterator.next() : null; - // Trivial goto block are only kept if they are self-targeting or are targeted by - // fallthroughs. - BasicBlock blk = block; // Additional local for lambda below. - assert !block.isTrivialGoto() - || block.exit().asGoto().getTarget() == block - || code.entryBlock() == block - || block.getPredecessors().stream().anyMatch((b) -> b.exit().fallthroughBlock() == blk); - // Trivial goto blocks never target the next block (in that case there should just be a - // fallthrough). - assert !block.isTrivialGoto() || block.exit().asGoto().getTarget() != nextBlock; - block = nextBlock; - } while (block != null); - return true; - } - // Rewrite 'throw new NullPointerException()' to 'throw null'. public void rewriteThrowNullPointerException(IRCode code) { boolean shouldRemoveUnreachableBlocks = false; @@ -363,105 +339,6 @@ public void rewriteThrowNullPointerException(IRCode code) { assert code.isConsistentSSA(appView); } - public static boolean isFallthroughBlock(BasicBlock block) { - for (BasicBlock pred : block.getPredecessors()) { - if (pred.exit().fallthroughBlock() == block) { - return true; - } - } - return false; - } - - private static void collapseTrivialGoto( - IRCode code, BasicBlock block, BasicBlock nextBlock, List blocksToRemove) { - - // This is the base case for GOTO loops. - if (block.exit().asGoto().getTarget() == block) { - return; - } - - BasicBlock target = block.endOfGotoChain(); - - boolean needed = false; - - if (target == null) { - // This implies we are in a loop of GOTOs. In that case, we will iteratively remove each - // trivial GOTO one-by-one until the above base case (one block targeting itself) is left. - target = block.exit().asGoto().getTarget(); - } - - if (target != nextBlock) { - // Not targeting the fallthrough block, determine if we need this goto. We need it if - // a fallthrough can hit this block. That is the case if the block is the entry block - // or if one of the predecessors fall through to the block. - needed = code.entryBlock() == block || isFallthroughBlock(block); - } - - if (!needed) { - blocksToRemove.add(block); - unlinkTrivialGotoBlock(block, target); - } - } - - public static void unlinkTrivialGotoBlock(BasicBlock block, BasicBlock target) { - assert block.isTrivialGoto(); - for (BasicBlock pred : block.getPredecessors()) { - pred.replaceSuccessor(block, target); - } - for (BasicBlock succ : block.getSuccessors()) { - succ.getMutablePredecessors().remove(block); - } - for (BasicBlock pred : block.getPredecessors()) { - if (!target.getPredecessors().contains(pred)) { - target.getMutablePredecessors().add(pred); - } - } - } - - private static void collapseIfTrueTarget(BasicBlock block) { - If insn = block.exit().asIf(); - BasicBlock target = insn.getTrueTarget(); - BasicBlock newTarget = target.endOfGotoChain(); - BasicBlock fallthrough = insn.fallthroughBlock(); - BasicBlock newFallthrough = fallthrough.endOfGotoChain(); - if (newTarget != null && target != newTarget) { - insn.getBlock().replaceSuccessor(target, newTarget); - target.getMutablePredecessors().remove(block); - if (!newTarget.getPredecessors().contains(block)) { - newTarget.getMutablePredecessors().add(block); - } - } - if (block.exit().isIf()) { - insn = block.exit().asIf(); - if (insn.getTrueTarget() == newFallthrough) { - // Replace if with the same true and fallthrough target with a goto to the fallthrough. - block.replaceSuccessor(insn.getTrueTarget(), fallthrough); - assert block.exit().isGoto(); - assert block.exit().asGoto().getTarget() == fallthrough; - } - } - } - - private static void collapseNonFallthroughSwitchTargets(BasicBlock block) { - Switch insn = block.exit().asSwitch(); - BasicBlock fallthroughBlock = insn.fallthroughBlock(); - Set replacedBlocks = new HashSet<>(); - for (int j = 0; j < insn.targetBlockIndices().length; j++) { - BasicBlock target = insn.targetBlock(j); - if (target != fallthroughBlock) { - BasicBlock newTarget = target.endOfGotoChain(); - if (newTarget != null && target != newTarget && !replacedBlocks.contains(target)) { - insn.getBlock().replaceSuccessor(target, newTarget); - target.getMutablePredecessors().remove(block); - if (!newTarget.getPredecessors().contains(block)) { - newTarget.getMutablePredecessors().add(block); - } - replacedBlocks.add(target); - } - } - } - } - // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings. public abstract static class InstructionBuilder { @@ -600,53 +477,6 @@ public BasicBlock build() { } } - /** - * Rewrite all branch targets to the destination of trivial goto chains when possible. Does not - * rewrite fallthrough targets as that would require block reordering and the transformation only - * makes sense after SSA destruction where there are no phis. - */ - public static void collapseTrivialGotos(AppView appView, IRCode code) { - assert code.isConsistentGraph(appView); - List blocksToRemove = new ArrayList<>(); - // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove - // first round of trivial goto blocks. - ListIterator iterator = code.listIterator(); - assert iterator.hasNext(); - BasicBlock block = iterator.next(); - BasicBlock nextBlock; - - do { - nextBlock = iterator.hasNext() ? iterator.next() : null; - if (block.isTrivialGoto()) { - collapseTrivialGoto(code, block, nextBlock, blocksToRemove); - } - if (block.exit().isIf()) { - collapseIfTrueTarget(block); - } - if (block.exit().isSwitch()) { - collapseNonFallthroughSwitchTargets(block); - } - block = nextBlock; - } while (nextBlock != null); - code.removeBlocks(blocksToRemove); - // Get rid of gotos to the next block. - while (!blocksToRemove.isEmpty()) { - blocksToRemove = new ArrayList<>(); - iterator = code.listIterator(); - block = iterator.next(); - do { - nextBlock = iterator.hasNext() ? iterator.next() : null; - if (block.isTrivialGoto()) { - collapseTrivialGoto(code, block, nextBlock, blocksToRemove); - } - block = nextBlock; - } while (block != null); - code.removeBlocks(blocksToRemove); - } - assert removedTrivialGotos(code); - assert code.isConsistentGraph(appView); - } - private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) { // TODO(sgjesse): Insert cast if required. TypeElement returnType = diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java index 019431d124..8a8ab5172b 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java @@ -29,6 +29,7 @@ import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; +import com.android.tools.r8.ir.conversion.passes.TrivialGotosCollapser; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; @@ -107,7 +108,7 @@ public void trivialGotoInEntryBlock() throws Exception { IRMetadata.unknown(), Origin.unknown(), new MutableMethodConversionOptions(options)); - CodeRewriter.collapseTrivialGotos(appView, code); + new TrivialGotosCollapser(appView).run(code.context(), code); assertTrue(code.entryBlock().isTrivialGoto()); assertTrue(blocks.contains(block0)); assertTrue(blocks.contains(block1)); @@ -196,7 +197,7 @@ public void trivialGotoLoopAsFallthrough() throws Exception { IRMetadata.unknown(), Origin.unknown(), new MutableMethodConversionOptions(options)); - CodeRewriter.collapseTrivialGotos(appView, code); + new TrivialGotosCollapser(appView).run(code.context(), code); assertTrue(block0.getInstructions().get(1).isIf()); assertEquals(block1, block0.getInstructions().get(1).asIf().fallthroughBlock()); assertTrue(blocks.containsAll(ImmutableList.of(block0, block1, block2, block3))); From f11b13e19c725e719a3debfdc79dac3039073502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 8 Jun 2023 10:52:38 +0200 Subject: [PATCH 036/153] Split TrivialCheckCastAndInstanceOfRemoval Bug: b/284304606 Change-Id: Ic514a8213a939e6d8177336c5a3d6e890a15fb61 --- .../tools/r8/ir/conversion/IRConverter.java | 9 +- .../TrivialCheckCastAndInstanceOfRemover.java | 383 ++++++++++++++++++ .../tools/r8/ir/optimize/CodeRewriter.java | 346 ---------------- .../optimize/classinliner/ClassInliner.java | 5 +- 4 files changed, 391 insertions(+), 352 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 5cc5c1c7fa..f1444c888b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -33,6 +33,7 @@ import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; +import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer; @@ -729,8 +730,8 @@ Timing optimize( assert code.verifyTypes(appView); timing.begin("Remove trivial type checks/casts"); - codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( - code, context, methodProcessor, methodProcessingContext); + new TrivialCheckCastAndInstanceOfRemover(appView) + .run(code, context, methodProcessor, methodProcessingContext); timing.end(); if (enumValueOptimizer != null) { @@ -774,8 +775,8 @@ Timing optimize( timing.begin("Simplify control flow"); if (new BranchSimplifier(appView).simplifyBranches(code)) { timing.begin("Remove trivial type checks/casts"); - codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( - code, context, methodProcessor, methodProcessingContext); + new TrivialCheckCastAndInstanceOfRemover(appView) + .run(code, context, methodProcessor, methodProcessingContext); timing.end(); } timing.end(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java new file mode 100644 index 0000000000..35d12534ea --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java @@ -0,0 +1,383 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; + +import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; +import com.android.tools.r8.graph.AccessControl; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; +import com.android.tools.r8.ir.analysis.type.Nullability; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; +import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.analysis.type.TypeUtils; +import com.android.tools.r8.ir.code.CheckCast; +import com.android.tools.r8.ir.code.ConstNumber; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.IRMetadata; +import com.android.tools.r8.ir.code.InstanceOf; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeStatic; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations; +import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.InternalOptions; +import com.google.common.collect.Sets; +import java.util.Set; + +public class TrivialCheckCastAndInstanceOfRemover { + + private final AppView appView; + private final InternalOptions options; + private final DexItemFactory dexItemFactory; + + public TrivialCheckCastAndInstanceOfRemover(AppView appView) { + this.appView = appView; + this.options = appView.options(); + this.dexItemFactory = appView.dexItemFactory(); + } + + public void run( + IRCode code, + ProgramMethod context, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext) { + if (!appView.enableWholeProgramOptimizations()) { + return; + } + + assert appView.appInfo().hasLiveness(); + AppView appViewWithLiveness = appView.withLiveness(); + + if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) { + return; + } + + IRMetadata metadata = code.metadata(); + if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) { + return; + } + + // If we can remove a CheckCast it is due to us having at least as much information about the + // type as the CheckCast gives. We then need to propagate that information to the users of + // the CheckCast to ensure further optimizations and removals of CheckCast: + // + // : 1: NewArrayEmpty v2 <- v1(1) java.lang.String[] <-- v2 = String[] + // ... + // : 2: CheckCast v5 <- v2; java.lang.Object[] <-- v5 = Object[] + // ... + // : 3: ArrayGet v7 <- v5, v6(0) <-- v7 = Object + // : 4: CheckCast v8 <- v7; java.lang.String <-- v8 = String + // ... + // + // When looking at line 2 we can conclude that the CheckCast is trivial because v2 is String[] + // and remove it. However, v7 is still only known to be Object and we cannot remove the + // CheckCast at line 4 unless we update v7 with the most precise information by narrowing the + // affected values of v5. We therefore have to run the type analysis after each CheckCast + // removal. + TypeAnalysis typeAnalysis = new TypeAnalysis(appView); + Set affectedValues = Sets.newIdentityHashSet(); + InstructionListIterator it = code.instructionListIterator(); + boolean needToRemoveTrivialPhis = false; + while (it.hasNext()) { + Instruction current = it.next(); + if (current.isCheckCast()) { + boolean hasPhiUsers = current.outValue().hasPhiUsers(); + RemoveCheckCastInstructionIfTrivialResult removeResult = + removeCheckCastInstructionIfTrivial( + appViewWithLiveness, + current.asCheckCast(), + it, + code, + context, + affectedValues, + methodProcessor, + methodProcessingContext); + if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) { + assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + needToRemoveTrivialPhis |= hasPhiUsers; + typeAnalysis.narrowing(affectedValues); + affectedValues.clear(); + } + } else if (current.isInstanceOf()) { + boolean hasPhiUsers = current.outValue().hasPhiUsers(); + if (removeInstanceOfInstructionIfTrivial( + appViewWithLiveness, current.asInstanceOf(), it, code)) { + needToRemoveTrivialPhis |= hasPhiUsers; + } + } + } + // ... v1 + // ... + // v2 <- check-cast v1, T + // v3 <- phi(v1, v2) + // Removing check-cast may result in a trivial phi: + // v3 <- phi(v1, v1) + if (needToRemoveTrivialPhis) { + code.removeAllDeadAndTrivialPhis(affectedValues); + if (!affectedValues.isEmpty()) { + typeAnalysis.narrowing(affectedValues); + } + } + assert code.isConsistentSSA(appView); + } + + enum RemoveCheckCastInstructionIfTrivialResult { + NO_REMOVALS, + REMOVED_CAST_DO_NARROW + } + + private enum InstanceOfResult { + UNKNOWN, + TRUE, + FALSE + } + + // Returns true if the given check-cast instruction was removed. + private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial( + AppView appViewWithLiveness, + CheckCast checkCast, + InstructionListIterator it, + IRCode code, + ProgramMethod context, + Set affectedValues, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext) { + Value inValue = checkCast.object(); + Value outValue = checkCast.outValue(); + DexType castType = checkCast.getType(); + DexType baseCastType = castType.toBaseType(dexItemFactory); + + // If the cast type is not accessible in the current context, we should not remove the cast + // in order to preserve runtime errors. Note that JVM and ART behave differently: see + // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}. + if (baseCastType.isClassType()) { + DexClass baseCastClass = appView.definitionFor(baseCastType); + if (baseCastClass == null + || AccessControl.isClassAccessible(baseCastClass, code.context(), appViewWithLiveness) + .isPossiblyFalse()) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + } + + if (!appView + .getOpenClosedInterfacesCollection() + .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + + // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast + // elimination may lead to verification errors. See b/123269162. + if (options.canHaveArtCheckCastVerifierBug()) { + if (inValue.getType().isNullType() + && castType.isArrayType() + && castType.toBaseType(dexItemFactory).isFloatType()) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + } + + // If casting to an array of an interface type elimination may lead to verification errors. + // See b/132420510 and b/223424356. + if (options.canHaveIncorrectJoinForArrayOfInterfacesBug()) { + if (castType.isArrayType()) { + DexType baseType = castType.toBaseType(dexItemFactory); + if (baseType.isClassType() && baseType.isInterface(appViewWithLiveness)) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + } + } + + TypeElement inTypeLattice = inValue.getType(); + TypeElement outTypeLattice = outValue.getType(); + TypeElement castTypeLattice = castType.toTypeElement(appView, inTypeLattice.nullability()); + + assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability()); + + if (inTypeLattice.lessThanOrEqual(castTypeLattice, appView)) { + // 1) Trivial cast. + // A a = ... + // A a' = (A) a; + // 2) Up-cast: we already have finer type info. + // A < B + // A a = ... + // B b = (B) a; + assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView); + // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast + // to the users of the CheckCast's out value. + // + // v2 = CheckCast A v1 ~~> DebugLocalWrite $v0 <- v1 + // + // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the + // CheckCast invalue users that includes the potential DebugLocalWrite. + removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue); + affectedValues.addAll(inValue.affectedValues()); + return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + } + + // If values of cast type are guaranteed to be null, then the out-value must be null if the cast + // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced + // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast + // type. + if (castType.isClassType() + && castType.isAlwaysNull(appViewWithLiveness) + && !outValue.hasDebugUsers()) { + // Replace all usages of the out-value by null. + it.previous(); + Value nullValue = it.insertConstNullInstruction(code, options); + it.next(); + checkCast.outValue().replaceUsers(nullValue); + affectedValues.addAll(nullValue.affectedValues()); + + // Replace the check-cast instruction by throwClassCastExceptionIfNotNull(). + UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod = + UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod( + appView, methodProcessor.getEventConsumer(), methodProcessingContext); + throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor); + InvokeStatic replacement = + InvokeStatic.builder() + .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod()) + .setSingleArgument(checkCast.object()) + .setPosition(checkCast) + .build(); + it.replaceCurrentInstruction(replacement); + assert replacement.lookupSingleTarget(appView, context) != null; + return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + } + + // If the cast is guaranteed to succeed and only there to ensure the program type checks, then + // check if the program would still type check after removing the cast. + if (checkCast.isSafeCheckCast() + || checkCast + .getFirstOperand() + .getDynamicType(appViewWithLiveness) + .getDynamicUpperBoundType() + .lessThanOrEqualUpToNullability(castTypeLattice, appView)) { + TypeElement useType = + TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue()); + if (inTypeLattice.lessThanOrEqualUpToNullability(useType, appView)) { + return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + } + } + + // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast: + // A < B < C + // c = ... // Even though we know c is of type A, + // a' = (B) c; // (this could be removed, since chained below.) + // a'' = (A) a'; // this should remain for runtime verification. + assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType()); + assert outTypeLattice.equalUpToNullability(castTypeLattice); + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + + // Returns true if the given instance-of instruction was removed. + private boolean removeInstanceOfInstructionIfTrivial( + AppView appViewWithLiveness, + InstanceOf instanceOf, + InstructionListIterator it, + IRCode code) { + ProgramMethod context = code.context(); + + // If the instance-of type is not accessible in the current context, we should not remove the + // instance-of instruction in order to preserve IllegalAccessError. + DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory); + if (instanceOfBaseType.isClassType()) { + DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType); + if (instanceOfClass == null + || AccessControl.isClassAccessible(instanceOfClass, context, appViewWithLiveness) + .isPossiblyFalse()) { + return false; + } + } + + Value inValue = instanceOf.value(); + if (!appView + .getOpenClosedInterfacesCollection() + .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { + return false; + } + + TypeElement inType = inValue.getType(); + TypeElement instanceOfType = + TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView); + Value aliasValue = inValue.getAliasedValue(); + + InstanceOfResult result = InstanceOfResult.UNKNOWN; + if (inType.isDefinitelyNull()) { + result = InstanceOfResult.FALSE; + } else if (inType.lessThanOrEqual(instanceOfType, appView) && !inType.isNullable()) { + result = InstanceOfResult.TRUE; + } else if (!aliasValue.isPhi() + && aliasValue.definition.isCreatingInstanceOrArray() + && instanceOfType.strictlyLessThan(inType, appView)) { + result = InstanceOfResult.FALSE; + } else if (appView.appInfo().hasLiveness()) { + if (instanceOf.type().isClassType() + && isNeverInstantiatedDirectlyOrIndirectly(instanceOf.type())) { + // The type of the instance-of instruction is a program class, and is never instantiated + // directly or indirectly. Thus, the in-value must be null, meaning that the instance-of + // instruction will always evaluate to false. + result = InstanceOfResult.FALSE; + } + + if (result == InstanceOfResult.UNKNOWN) { + if (inType.isClassType() + && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) { + // The type of the in-value is a program class, and is never instantiated directly or + // indirectly. This, the in-value must be null, meaning that the instance-of instruction + // will always evaluate to false. + result = InstanceOfResult.FALSE; + } + } + + if (result == InstanceOfResult.UNKNOWN) { + Value aliasedValue = + inValue.getSpecificAliasedValue( + value -> + value.isDefinedByInstructionSatisfying( + Instruction::isAssumeWithDynamicTypeAssumption)); + if (aliasedValue != null) { + DynamicTypeWithUpperBound dynamicType = + aliasedValue.getDefinition().asAssume().getDynamicTypeAssumption().getDynamicType(); + Nullability nullability = dynamicType.getNullability(); + if (nullability.isDefinitelyNull()) { + result = InstanceOfResult.FALSE; + } else if (dynamicType.getDynamicUpperBoundType().lessThanOrEqual(instanceOfType, appView) + && (!inType.isNullable() || !nullability.isNullable())) { + result = InstanceOfResult.TRUE; + } + } + } + } + if (result != InstanceOfResult.UNKNOWN) { + ConstNumber newInstruction = + new ConstNumber( + new Value( + code.valueNumberGenerator.next(), + TypeElement.getInt(), + instanceOf.outValue().getLocalInfo()), + result == InstanceOfResult.TRUE ? 1 : 0); + it.replaceCurrentInstruction(newInstruction); + return true; + } + return false; + } + + private boolean isNeverInstantiatedDirectlyOrIndirectly(DexType type) { + assert appView.appInfo().hasLiveness(); + assert type.isClassType(); + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); + return clazz != null + && !appView.appInfo().withLiveness().isInstantiatedDirectlyOrIndirectly(clazz); + } +} 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 719a05aeed..4778cef305 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 @@ -4,7 +4,6 @@ package com.android.tools.r8.ir.optimize; -import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS; @@ -14,9 +13,7 @@ import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; -import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.graph.AccessControl; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DebugLocalInfo; @@ -25,16 +22,12 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; -import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; -import com.android.tools.r8.ir.analysis.type.TypeUtils; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.code.ArrayLength; import com.android.tools.r8.ir.code.Assume; @@ -43,7 +36,6 @@ import com.android.tools.r8.ir.code.Binop; import com.android.tools.r8.ir.code.CatchHandlers; import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; -import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstClass; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; @@ -58,7 +50,6 @@ import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.InstanceFieldInstruction; import com.android.tools.r8.ir.code.InstanceGet; -import com.android.tools.r8.ir.code.InstanceOf; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; import com.android.tools.r8.ir.code.InstructionIterator; @@ -70,7 +61,6 @@ import com.android.tools.r8.ir.code.InvokeInterface; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; -import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.NewInstance; @@ -80,11 +70,8 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.conversion.MethodProcessor; -import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; -import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; @@ -122,12 +109,6 @@ public class CodeRewriter { - private enum InstanceOfResult { - UNKNOWN, - TRUE, - FALSE - } - // This constant was determined by experimentation. private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50; @@ -582,333 +563,6 @@ public boolean rewriteMoveResult(IRCode code) { return changed; } - enum RemoveCheckCastInstructionIfTrivialResult { - NO_REMOVALS, - REMOVED_CAST_DO_NARROW - } - - public void removeTrivialCheckCastAndInstanceOfInstructions( - IRCode code, - ProgramMethod context, - MethodProcessor methodProcessor, - MethodProcessingContext methodProcessingContext) { - if (!appView.enableWholeProgramOptimizations()) { - return; - } - - assert appView.appInfo().hasLiveness(); - AppView appViewWithLiveness = appView.withLiveness(); - - if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) { - return; - } - - IRMetadata metadata = code.metadata(); - if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) { - return; - } - - // If we can remove a CheckCast it is due to us having at least as much information about the - // type as the CheckCast gives. We then need to propagate that information to the users of - // the CheckCast to ensure further optimizations and removals of CheckCast: - // - // : 1: NewArrayEmpty v2 <- v1(1) java.lang.String[] <-- v2 = String[] - // ... - // : 2: CheckCast v5 <- v2; java.lang.Object[] <-- v5 = Object[] - // ... - // : 3: ArrayGet v7 <- v5, v6(0) <-- v7 = Object - // : 4: CheckCast v8 <- v7; java.lang.String <-- v8 = String - // ... - // - // When looking at line 2 we can conclude that the CheckCast is trivial because v2 is String[] - // and remove it. However, v7 is still only known to be Object and we cannot remove the - // CheckCast at line 4 unless we update v7 with the most precise information by narrowing the - // affected values of v5. We therefore have to run the type analysis after each CheckCast - // removal. - TypeAnalysis typeAnalysis = new TypeAnalysis(appView); - Set affectedValues = Sets.newIdentityHashSet(); - InstructionListIterator it = code.instructionListIterator(); - boolean needToRemoveTrivialPhis = false; - while (it.hasNext()) { - Instruction current = it.next(); - if (current.isCheckCast()) { - boolean hasPhiUsers = current.outValue().hasPhiUsers(); - RemoveCheckCastInstructionIfTrivialResult removeResult = - removeCheckCastInstructionIfTrivial( - appViewWithLiveness, - current.asCheckCast(), - it, - code, - context, - affectedValues, - methodProcessor, - methodProcessingContext); - if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) { - assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - needToRemoveTrivialPhis |= hasPhiUsers; - typeAnalysis.narrowing(affectedValues); - affectedValues.clear(); - } - } else if (current.isInstanceOf()) { - boolean hasPhiUsers = current.outValue().hasPhiUsers(); - if (removeInstanceOfInstructionIfTrivial( - appViewWithLiveness, current.asInstanceOf(), it, code)) { - needToRemoveTrivialPhis |= hasPhiUsers; - } - } - } - // ... v1 - // ... - // v2 <- check-cast v1, T - // v3 <- phi(v1, v2) - // Removing check-cast may result in a trivial phi: - // v3 <- phi(v1, v1) - if (needToRemoveTrivialPhis) { - code.removeAllDeadAndTrivialPhis(affectedValues); - if (!affectedValues.isEmpty()) { - typeAnalysis.narrowing(affectedValues); - } - } - assert code.isConsistentSSA(appView); - } - - // Returns true if the given check-cast instruction was removed. - private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial( - AppView appViewWithLiveness, - CheckCast checkCast, - InstructionListIterator it, - IRCode code, - ProgramMethod context, - Set affectedValues, - MethodProcessor methodProcessor, - MethodProcessingContext methodProcessingContext) { - Value inValue = checkCast.object(); - Value outValue = checkCast.outValue(); - DexType castType = checkCast.getType(); - DexType baseCastType = castType.toBaseType(dexItemFactory); - - // If the cast type is not accessible in the current context, we should not remove the cast - // in order to preserve runtime errors. Note that JVM and ART behave differently: see - // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}. - if (baseCastType.isClassType()) { - DexClass baseCastClass = appView.definitionFor(baseCastType); - if (baseCastClass == null - || AccessControl.isClassAccessible(baseCastClass, code.context(), appViewWithLiveness) - .isPossiblyFalse()) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - } - - if (!appView - .getOpenClosedInterfacesCollection() - .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - - // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast - // elimination may lead to verification errors. See b/123269162. - if (options.canHaveArtCheckCastVerifierBug()) { - if (inValue.getType().isNullType() - && castType.isArrayType() - && castType.toBaseType(dexItemFactory).isFloatType()) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - } - - // If casting to an array of an interface type elimination may lead to verification errors. - // See b/132420510 and b/223424356. - if (options.canHaveIncorrectJoinForArrayOfInterfacesBug()) { - if (castType.isArrayType()) { - DexType baseType = castType.toBaseType(dexItemFactory); - if (baseType.isClassType() && baseType.isInterface(appViewWithLiveness)) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - } - } - - TypeElement inTypeLattice = inValue.getType(); - TypeElement outTypeLattice = outValue.getType(); - TypeElement castTypeLattice = castType.toTypeElement(appView, inTypeLattice.nullability()); - - assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability()); - - if (inTypeLattice.lessThanOrEqual(castTypeLattice, appView)) { - // 1) Trivial cast. - // A a = ... - // A a' = (A) a; - // 2) Up-cast: we already have finer type info. - // A < B - // A a = ... - // B b = (B) a; - assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView); - // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast - // to the users of the CheckCast's out value. - // - // v2 = CheckCast A v1 ~~> DebugLocalWrite $v0 <- v1 - // - // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the - // CheckCast invalue users that includes the potential DebugLocalWrite. - removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue); - affectedValues.addAll(inValue.affectedValues()); - return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - } - - // If values of cast type are guaranteed to be null, then the out-value must be null if the cast - // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced - // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast - // type. - if (castType.isClassType() - && castType.isAlwaysNull(appViewWithLiveness) - && !outValue.hasDebugUsers()) { - // Replace all usages of the out-value by null. - it.previous(); - Value nullValue = it.insertConstNullInstruction(code, options); - it.next(); - checkCast.outValue().replaceUsers(nullValue); - affectedValues.addAll(nullValue.affectedValues()); - - // Replace the check-cast instruction by throwClassCastExceptionIfNotNull(). - UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod = - UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod( - appView, methodProcessor.getEventConsumer(), methodProcessingContext); - throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor); - InvokeStatic replacement = - InvokeStatic.builder() - .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod()) - .setSingleArgument(checkCast.object()) - .setPosition(checkCast) - .build(); - it.replaceCurrentInstruction(replacement); - assert replacement.lookupSingleTarget(appView, context) != null; - return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - } - - // If the cast is guaranteed to succeed and only there to ensure the program type checks, then - // check if the program would still type check after removing the cast. - if (checkCast.isSafeCheckCast() - || checkCast - .getFirstOperand() - .getDynamicType(appViewWithLiveness) - .getDynamicUpperBoundType() - .lessThanOrEqualUpToNullability(castTypeLattice, appView)) { - TypeElement useType = - TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue()); - if (inTypeLattice.lessThanOrEqualUpToNullability(useType, appView)) { - return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - } - } - - // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast: - // A < B < C - // c = ... // Even though we know c is of type A, - // a' = (B) c; // (this could be removed, since chained below.) - // a'' = (A) a'; // this should remain for runtime verification. - assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType()); - assert outTypeLattice.equalUpToNullability(castTypeLattice); - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - - // Returns true if the given instance-of instruction was removed. - private boolean removeInstanceOfInstructionIfTrivial( - AppView appViewWithLiveness, - InstanceOf instanceOf, - InstructionListIterator it, - IRCode code) { - ProgramMethod context = code.context(); - - // If the instance-of type is not accessible in the current context, we should not remove the - // instance-of instruction in order to preserve IllegalAccessError. - DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory); - if (instanceOfBaseType.isClassType()) { - DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType); - if (instanceOfClass == null - || AccessControl.isClassAccessible(instanceOfClass, context, appViewWithLiveness) - .isPossiblyFalse()) { - return false; - } - } - - Value inValue = instanceOf.value(); - if (!appView - .getOpenClosedInterfacesCollection() - .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { - return false; - } - - TypeElement inType = inValue.getType(); - TypeElement instanceOfType = - TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView); - Value aliasValue = inValue.getAliasedValue(); - - InstanceOfResult result = InstanceOfResult.UNKNOWN; - if (inType.isDefinitelyNull()) { - result = InstanceOfResult.FALSE; - } else if (inType.lessThanOrEqual(instanceOfType, appView) && !inType.isNullable()) { - result = InstanceOfResult.TRUE; - } else if (!aliasValue.isPhi() - && aliasValue.definition.isCreatingInstanceOrArray() - && instanceOfType.strictlyLessThan(inType, appView)) { - result = InstanceOfResult.FALSE; - } else if (appView.appInfo().hasLiveness()) { - if (instanceOf.type().isClassType() - && isNeverInstantiatedDirectlyOrIndirectly(instanceOf.type())) { - // The type of the instance-of instruction is a program class, and is never instantiated - // directly or indirectly. Thus, the in-value must be null, meaning that the instance-of - // instruction will always evaluate to false. - result = InstanceOfResult.FALSE; - } - - if (result == InstanceOfResult.UNKNOWN) { - if (inType.isClassType() - && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) { - // The type of the in-value is a program class, and is never instantiated directly or - // indirectly. This, the in-value must be null, meaning that the instance-of instruction - // will always evaluate to false. - result = InstanceOfResult.FALSE; - } - } - - if (result == InstanceOfResult.UNKNOWN) { - Value aliasedValue = - inValue.getSpecificAliasedValue( - value -> - value.isDefinedByInstructionSatisfying( - Instruction::isAssumeWithDynamicTypeAssumption)); - if (aliasedValue != null) { - DynamicTypeWithUpperBound dynamicType = - aliasedValue.getDefinition().asAssume().getDynamicTypeAssumption().getDynamicType(); - Nullability nullability = dynamicType.getNullability(); - if (nullability.isDefinitelyNull()) { - result = InstanceOfResult.FALSE; - } else if (dynamicType.getDynamicUpperBoundType().lessThanOrEqual(instanceOfType, appView) - && (!inType.isNullable() || !nullability.isNullable())) { - result = InstanceOfResult.TRUE; - } - } - } - } - if (result != InstanceOfResult.UNKNOWN) { - ConstNumber newInstruction = - new ConstNumber( - new Value( - code.valueNumberGenerator.next(), - TypeElement.getInt(), - instanceOf.outValue().getLocalInfo()), - result == InstanceOfResult.TRUE ? 1 : 0); - it.replaceCurrentInstruction(newInstruction); - return true; - } - return false; - } - - private boolean isNeverInstantiatedDirectlyOrIndirectly(DexType type) { - assert appView.appInfo().hasLiveness(); - assert type.isClassType(); - DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); - return clazz != null - && !appView.appInfo().withLiveness().isInstantiatedDirectlyOrIndirectly(clazz); - } - public static void removeOrReplaceByDebugLocalWrite( Instruction currentInstruction, InstructionListIterator it, Value inValue, Value outValue) { if (outValue.hasLocalInfo() && outValue.getLocalInfo() != inValue.getLocalInfo()) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index 435b01ca13..b1ec0130a2 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -20,6 +20,7 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; +import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; import com.android.tools.r8.ir.optimize.AssumeRemover; import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner; @@ -248,8 +249,8 @@ public final void processMethodCode( // If a method was inlined we may be able to remove check-cast instructions because we may // have more information about the types of the arguments at the call site. This is // particularly important for bridge methods. - codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( - code, method, methodProcessor, methodProcessingContext); + new TrivialCheckCastAndInstanceOfRemover(appView) + .run(code, method, methodProcessor, methodProcessingContext); // If a method was inlined we may be able to prune additional branches. new BranchSimplifier(appView).simplifyBranches(code); // If a method was inlined we may see more trivial computation/conversion of String. From 1a8ccad64f1bb2194fc94ae78c9972a8a989b33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gjesse?= Date: Tue, 6 Jun 2023 15:00:46 +0200 Subject: [PATCH 037/153] Add LICENSE file to the desugar_jdk_libs_configuration artifact Use the R8 license file, as desugar_jdk_libr_configuration os provided by the R8 authors. Bug: b/285654539 Change-Id: I121f64f5445703b67cfafd221a83ad5516b3f653 --- tools/create_maven_release.py | 40 +++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py index ee68e1ebf7..1ca239e52b 100755 --- a/tools/create_maven_release.py +++ b/tools/create_maven_release.py @@ -124,6 +124,12 @@ def parse_options(argv): help='Build desugar library configuration (original JDK-8)') group.add_argument('--desugar-configuration-jdk11-legacy', action='store_true', help='Build desugar library configuration (JDK-11 legacy)') + group.add_argument('--desugar-configuration-jdk11-minimal', action='store_true', + help='Build desugar library configuration (JDK-11 minimal)') + group.add_argument('--desugar-configuration-jdk11', action='store_true', + help='Build desugar library configuration (JDK-11)') + group.add_argument('--desugar-configuration-jdk11-nio', action='store_true', + help='Build desugar library configuration (JDK-11 nio)') return result.parse_args(argv) def determine_version(): @@ -369,7 +375,7 @@ def generate_jar_with_desugar_configuration( with zipfile.ZipFile(conversions, 'r') as conversions_zip: conversions_zip.extractall(tmp_dir) - # Add configuration + # Add configuration. configuration_dir = join(tmp_dir, 'META-INF', 'desugar', 'd8') makedirs(configuration_dir) copyfile(configuration, join(configuration_dir, 'desugar.json')) @@ -388,6 +394,9 @@ def generate_jar_with_desugar_configuration( utils.PrintCmd(cmd) subprocess.check_call(cmd) + # Add LICENSE file. + copyfile(join(utils.REPO_ROOT, 'LICENSE'), join(tmp_dir, 'LICENSE')) + make_archive(destination, 'zip', tmp_dir) move(destination + '.zip', destination) @@ -435,11 +444,34 @@ def main(argv): 'Need to supply output zip with --out.') if options.desugar_configuration or options.desugar_configuration_jdk8: generate_desugar_configuration_maven_zip( - options.out, utils.DESUGAR_CONFIGURATION, utils.DESUGAR_IMPLEMENTATION) + options.out, + utils.DESUGAR_CONFIGURATION, + utils.DESUGAR_IMPLEMENTATION, + utils.LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP) elif options.desugar_configuration_jdk11_legacy: generate_desugar_configuration_maven_zip( - options.out, utils.DESUGAR_CONFIGURATION_JDK11_LEGACY, - utils.DESUGAR_IMPLEMENTATION_JDK11) + options.out, + utils.DESUGAR_CONFIGURATION_JDK11_LEGACY, + utils.DESUGAR_IMPLEMENTATION_JDK11, + utils.LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP) + elif options.desugar_configuration_jdk11_minimal: + generate_desugar_configuration_maven_zip( + options.out, + utils.DESUGAR_CONFIGURATION_JDK11_MINIMAL, + utils.DESUGAR_IMPLEMENTATION_JDK11, + utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP) + elif options.desugar_configuration_jdk11: + generate_desugar_configuration_maven_zip( + options.out, + utils.DESUGAR_CONFIGURATION_JDK11, + utils.DESUGAR_IMPLEMENTATION_JDK11, + utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP) + elif options.desugar_configuration_jdk11_nio: + generate_desugar_configuration_maven_zip( + options.out, + utils.DESUGAR_CONFIGURATION_JDK11_NIO, + utils.DESUGAR_IMPLEMENTATION_JDK11, + utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP) else: generate_r8_maven_zip(options.out, options.r8lib) From 4f47f394830db30517c74b807c39b84a97e191eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 8 Jun 2023 11:12:44 +0200 Subject: [PATCH 038/153] Move inner classes to correct top classes - and minor dead code and double white line Bug: b/284304606 Change-Id: Iac3a9c7d31276dadaac39c23aed585f40f5331be --- .../tools/r8/ir/conversion/IRConverter.java | 1 - .../conversion/passes/BranchSimplifier.java | 145 ++++++++++++++++- .../tools/r8/ir/optimize/CodeRewriter.java | 146 ------------------ .../optimize/classinliner/ClassInliner.java | 2 - 4 files changed, 143 insertions(+), 151 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index f1444c888b..55c6a062fa 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -831,7 +831,6 @@ Timing optimize( assert options.inlinerOptions().enableInlining && inliner != null; classInliner.processMethodCode( appView.withLiveness(), - codeRewriter, stringOptimizer, enumValueOptimizer, code.context(), diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index 1cc873d002..199df6244e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -20,6 +20,7 @@ import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.InstanceGet; @@ -34,8 +35,6 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.code.Xor; -import com.android.tools.r8.ir.optimize.CodeRewriter.IfBuilder; -import com.android.tools.r8.ir.optimize.CodeRewriter.SwitchBuilder; import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; @@ -44,9 +43,13 @@ import com.android.tools.r8.utils.LongInterval; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -1087,4 +1090,142 @@ public void convertSwitchToSwitchAndIfs( // Finally add the blocks. newBlocks.forEach(blocksIterator::add); } + + // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings. + private abstract static class InstructionBuilder { + + int blockNumber; + final Position position; + + InstructionBuilder(Position position) { + this.position = position; + } + + abstract T self(); + + T setBlockNumber(int blockNumber) { + this.blockNumber = blockNumber; + return self(); + } + } + + private static class SwitchBuilder extends InstructionBuilder { + + private Value value; + private final Int2ReferenceSortedMap keyToTarget = new Int2ReferenceAVLTreeMap<>(); + private BasicBlock fallthrough; + + SwitchBuilder(Position position) { + super(position); + } + + @Override + SwitchBuilder self() { + return this; + } + + SwitchBuilder setValue(Value value) { + this.value = value; + return this; + } + + SwitchBuilder addKeyAndTarget(int key, BasicBlock target) { + keyToTarget.put(key, target); + return this; + } + + SwitchBuilder setFallthrough(BasicBlock fallthrough) { + this.fallthrough = fallthrough; + return this; + } + + BasicBlock build(IRMetadata metadata) { + final int NOT_FOUND = -1; + Object2IntMap targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>(); + targetToSuccessorIndex.defaultReturnValue(NOT_FOUND); + + int[] keys = new int[keyToTarget.size()]; + int[] targetBlockIndices = new int[keyToTarget.size()]; + // Sort keys descending. + int count = 0; + IntIterator iter = keyToTarget.keySet().iterator(); + while (iter.hasNext()) { + int key = iter.nextInt(); + BasicBlock target = keyToTarget.get(key); + Integer targetIndex = + targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size()); + keys[count] = key; + targetBlockIndices[count] = targetIndex; + count++; + } + Integer fallthroughIndex = + targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size()); + IntSwitch newSwitch = new IntSwitch(value, keys, targetBlockIndices, fallthroughIndex); + newSwitch.setPosition(position); + BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch, metadata); + for (BasicBlock successor : targetToSuccessorIndex.keySet()) { + newSwitchBlock.link(successor); + } + return newSwitchBlock; + } + } + + private static class IfBuilder extends InstructionBuilder { + + private final IRCode code; + private Value left; + private int right; + private BasicBlock target; + private BasicBlock fallthrough; + + IfBuilder(Position position, IRCode code) { + super(position); + this.code = code; + } + + @Override + IfBuilder self() { + return this; + } + + IfBuilder setLeft(Value left) { + this.left = left; + return this; + } + + IfBuilder setRight(int right) { + this.right = right; + return this; + } + + IfBuilder setTarget(BasicBlock target) { + this.target = target; + return this; + } + + IfBuilder setFallthrough(BasicBlock fallthrough) { + this.fallthrough = fallthrough; + return this; + } + + BasicBlock build() { + assert target != null; + assert fallthrough != null; + If newIf; + BasicBlock ifBlock; + if (right != 0) { + ConstNumber rightConst = code.createIntConstant(right); + rightConst.setPosition(position); + newIf = new If(IfType.EQ, ImmutableList.of(left, rightConst.dest())); + ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata(), rightConst); + } else { + newIf = new If(IfType.EQ, left); + ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata()); + } + newIf.setPosition(position); + ifBlock.link(target); + ifBlock.link(fallthrough); + return ifBlock; + } + } } 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 4778cef305..4ad6534ed0 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 @@ -45,7 +45,6 @@ import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.InstanceFieldInstruction; @@ -55,7 +54,6 @@ import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InstructionOrPhi; -import com.android.tools.r8.ir.code.IntSwitch; import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeInterface; @@ -80,18 +78,13 @@ import com.google.common.collect.Streams; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; -import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.ArrayList; @@ -320,144 +313,6 @@ public void rewriteThrowNullPointerException(IRCode code) { assert code.isConsistentSSA(appView); } - // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings. - public abstract static class InstructionBuilder { - - protected int blockNumber; - protected final Position position; - - protected InstructionBuilder(Position position) { - this.position = position; - } - - public abstract T self(); - - public T setBlockNumber(int blockNumber) { - this.blockNumber = blockNumber; - return self(); - } - } - - public static class SwitchBuilder extends InstructionBuilder { - - private Value value; - private final Int2ReferenceSortedMap keyToTarget = new Int2ReferenceAVLTreeMap<>(); - private BasicBlock fallthrough; - - public SwitchBuilder(Position position) { - super(position); - } - - @Override - public SwitchBuilder self() { - return this; - } - - public SwitchBuilder setValue(Value value) { - this.value = value; - return this; - } - - public SwitchBuilder addKeyAndTarget(int key, BasicBlock target) { - keyToTarget.put(key, target); - return this; - } - - public SwitchBuilder setFallthrough(BasicBlock fallthrough) { - this.fallthrough = fallthrough; - return this; - } - - public BasicBlock build(IRMetadata metadata) { - final int NOT_FOUND = -1; - Object2IntMap targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>(); - targetToSuccessorIndex.defaultReturnValue(NOT_FOUND); - - int[] keys = new int[keyToTarget.size()]; - int[] targetBlockIndices = new int[keyToTarget.size()]; - // Sort keys descending. - int count = 0; - IntIterator iter = keyToTarget.keySet().iterator(); - while (iter.hasNext()) { - int key = iter.nextInt(); - BasicBlock target = keyToTarget.get(key); - Integer targetIndex = - targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size()); - keys[count] = key; - targetBlockIndices[count] = targetIndex; - count++; - } - Integer fallthroughIndex = - targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size()); - IntSwitch newSwitch = new IntSwitch(value, keys, targetBlockIndices, fallthroughIndex); - newSwitch.setPosition(position); - BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch, metadata); - for (BasicBlock successor : targetToSuccessorIndex.keySet()) { - newSwitchBlock.link(successor); - } - return newSwitchBlock; - } - } - - public static class IfBuilder extends InstructionBuilder { - - private final IRCode code; - private Value left; - private int right; - private BasicBlock target; - private BasicBlock fallthrough; - - public IfBuilder(Position position, IRCode code) { - super(position); - this.code = code; - } - - @Override - public IfBuilder self() { - return this; - } - - public IfBuilder setLeft(Value left) { - this.left = left; - return this; - } - - public IfBuilder setRight(int right) { - this.right = right; - return this; - } - - public IfBuilder setTarget(BasicBlock target) { - this.target = target; - return this; - } - - public IfBuilder setFallthrough(BasicBlock fallthrough) { - this.fallthrough = fallthrough; - return this; - } - - public BasicBlock build() { - assert target != null; - assert fallthrough != null; - If newIf; - BasicBlock ifBlock; - if (right != 0) { - ConstNumber rightConst = code.createIntConstant(right); - rightConst.setPosition(position); - newIf = new If(IfType.EQ, ImmutableList.of(left, rightConst.dest())); - ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata(), rightConst); - } else { - newIf = new If(IfType.EQ, left); - ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata()); - } - newIf.setPosition(position); - ifBlock.link(target); - ifBlock.link(fallthrough); - return ifBlock; - } - } - private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) { // TODO(sgjesse): Insert cast if required. TypeElement returnType = @@ -1626,7 +1481,6 @@ public void optimizeAlwaysThrowingInstructions(IRCode code) { assert code.isConsistentSSA(appView); } - private void insertNotNullCheck( BasicBlock block, InstructionListIterator iterator, diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index b1ec0130a2..889a43448c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -22,7 +22,6 @@ import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; import com.android.tools.r8.ir.optimize.AssumeRemover; -import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.InliningOracle; import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException; @@ -128,7 +127,6 @@ enum EligibilityStatus { // public final void processMethodCode( AppView appView, - CodeRewriter codeRewriter, StringOptimizer stringOptimizer, EnumValueOptimizer enumValueOptimizer, ProgramMethod method, From e5be3448330016bb6006271207ee0290e21f8cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gjesse?= Date: Thu, 8 Jun 2023 10:02:46 +0000 Subject: [PATCH 039/153] Revert "Split TrivialCheckCastAndInstanceOfRemoval" This reverts commit f11b13e19c725e719a3debfdc79dac3039073502. Reason for revert: Fails compilation. Change-Id: I3e4a066f42e4fe4631126b34ca966561179b82ff --- .../tools/r8/ir/conversion/IRConverter.java | 9 +- .../TrivialCheckCastAndInstanceOfRemover.java | 383 ------------------ .../tools/r8/ir/optimize/CodeRewriter.java | 346 ++++++++++++++++ .../optimize/classinliner/ClassInliner.java | 5 +- 4 files changed, 352 insertions(+), 391 deletions(-) delete mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 55c6a062fa..fc1d15a557 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -33,7 +33,6 @@ import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; -import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer; @@ -730,8 +729,8 @@ Timing optimize( assert code.verifyTypes(appView); timing.begin("Remove trivial type checks/casts"); - new TrivialCheckCastAndInstanceOfRemover(appView) - .run(code, context, methodProcessor, methodProcessingContext); + codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( + code, context, methodProcessor, methodProcessingContext); timing.end(); if (enumValueOptimizer != null) { @@ -775,8 +774,8 @@ Timing optimize( timing.begin("Simplify control flow"); if (new BranchSimplifier(appView).simplifyBranches(code)) { timing.begin("Remove trivial type checks/casts"); - new TrivialCheckCastAndInstanceOfRemover(appView) - .run(code, context, methodProcessor, methodProcessingContext); + codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( + code, context, methodProcessor, methodProcessingContext); timing.end(); } timing.end(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java deleted file mode 100644 index 35d12534ea..0000000000 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright (c) 2023, 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.ir.conversion.passes; - -import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; - -import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; -import com.android.tools.r8.graph.AccessControl; -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexItemFactory; -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; -import com.android.tools.r8.ir.analysis.type.Nullability; -import com.android.tools.r8.ir.analysis.type.TypeAnalysis; -import com.android.tools.r8.ir.analysis.type.TypeElement; -import com.android.tools.r8.ir.analysis.type.TypeUtils; -import com.android.tools.r8.ir.code.CheckCast; -import com.android.tools.r8.ir.code.ConstNumber; -import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.IRMetadata; -import com.android.tools.r8.ir.code.InstanceOf; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InstructionListIterator; -import com.android.tools.r8.ir.code.InvokeStatic; -import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.conversion.MethodProcessor; -import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations; -import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.InternalOptions; -import com.google.common.collect.Sets; -import java.util.Set; - -public class TrivialCheckCastAndInstanceOfRemover { - - private final AppView appView; - private final InternalOptions options; - private final DexItemFactory dexItemFactory; - - public TrivialCheckCastAndInstanceOfRemover(AppView appView) { - this.appView = appView; - this.options = appView.options(); - this.dexItemFactory = appView.dexItemFactory(); - } - - public void run( - IRCode code, - ProgramMethod context, - MethodProcessor methodProcessor, - MethodProcessingContext methodProcessingContext) { - if (!appView.enableWholeProgramOptimizations()) { - return; - } - - assert appView.appInfo().hasLiveness(); - AppView appViewWithLiveness = appView.withLiveness(); - - if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) { - return; - } - - IRMetadata metadata = code.metadata(); - if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) { - return; - } - - // If we can remove a CheckCast it is due to us having at least as much information about the - // type as the CheckCast gives. We then need to propagate that information to the users of - // the CheckCast to ensure further optimizations and removals of CheckCast: - // - // : 1: NewArrayEmpty v2 <- v1(1) java.lang.String[] <-- v2 = String[] - // ... - // : 2: CheckCast v5 <- v2; java.lang.Object[] <-- v5 = Object[] - // ... - // : 3: ArrayGet v7 <- v5, v6(0) <-- v7 = Object - // : 4: CheckCast v8 <- v7; java.lang.String <-- v8 = String - // ... - // - // When looking at line 2 we can conclude that the CheckCast is trivial because v2 is String[] - // and remove it. However, v7 is still only known to be Object and we cannot remove the - // CheckCast at line 4 unless we update v7 with the most precise information by narrowing the - // affected values of v5. We therefore have to run the type analysis after each CheckCast - // removal. - TypeAnalysis typeAnalysis = new TypeAnalysis(appView); - Set affectedValues = Sets.newIdentityHashSet(); - InstructionListIterator it = code.instructionListIterator(); - boolean needToRemoveTrivialPhis = false; - while (it.hasNext()) { - Instruction current = it.next(); - if (current.isCheckCast()) { - boolean hasPhiUsers = current.outValue().hasPhiUsers(); - RemoveCheckCastInstructionIfTrivialResult removeResult = - removeCheckCastInstructionIfTrivial( - appViewWithLiveness, - current.asCheckCast(), - it, - code, - context, - affectedValues, - methodProcessor, - methodProcessingContext); - if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) { - assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - needToRemoveTrivialPhis |= hasPhiUsers; - typeAnalysis.narrowing(affectedValues); - affectedValues.clear(); - } - } else if (current.isInstanceOf()) { - boolean hasPhiUsers = current.outValue().hasPhiUsers(); - if (removeInstanceOfInstructionIfTrivial( - appViewWithLiveness, current.asInstanceOf(), it, code)) { - needToRemoveTrivialPhis |= hasPhiUsers; - } - } - } - // ... v1 - // ... - // v2 <- check-cast v1, T - // v3 <- phi(v1, v2) - // Removing check-cast may result in a trivial phi: - // v3 <- phi(v1, v1) - if (needToRemoveTrivialPhis) { - code.removeAllDeadAndTrivialPhis(affectedValues); - if (!affectedValues.isEmpty()) { - typeAnalysis.narrowing(affectedValues); - } - } - assert code.isConsistentSSA(appView); - } - - enum RemoveCheckCastInstructionIfTrivialResult { - NO_REMOVALS, - REMOVED_CAST_DO_NARROW - } - - private enum InstanceOfResult { - UNKNOWN, - TRUE, - FALSE - } - - // Returns true if the given check-cast instruction was removed. - private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial( - AppView appViewWithLiveness, - CheckCast checkCast, - InstructionListIterator it, - IRCode code, - ProgramMethod context, - Set affectedValues, - MethodProcessor methodProcessor, - MethodProcessingContext methodProcessingContext) { - Value inValue = checkCast.object(); - Value outValue = checkCast.outValue(); - DexType castType = checkCast.getType(); - DexType baseCastType = castType.toBaseType(dexItemFactory); - - // If the cast type is not accessible in the current context, we should not remove the cast - // in order to preserve runtime errors. Note that JVM and ART behave differently: see - // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}. - if (baseCastType.isClassType()) { - DexClass baseCastClass = appView.definitionFor(baseCastType); - if (baseCastClass == null - || AccessControl.isClassAccessible(baseCastClass, code.context(), appViewWithLiveness) - .isPossiblyFalse()) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - } - - if (!appView - .getOpenClosedInterfacesCollection() - .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - - // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast - // elimination may lead to verification errors. See b/123269162. - if (options.canHaveArtCheckCastVerifierBug()) { - if (inValue.getType().isNullType() - && castType.isArrayType() - && castType.toBaseType(dexItemFactory).isFloatType()) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - } - - // If casting to an array of an interface type elimination may lead to verification errors. - // See b/132420510 and b/223424356. - if (options.canHaveIncorrectJoinForArrayOfInterfacesBug()) { - if (castType.isArrayType()) { - DexType baseType = castType.toBaseType(dexItemFactory); - if (baseType.isClassType() && baseType.isInterface(appViewWithLiveness)) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - } - } - - TypeElement inTypeLattice = inValue.getType(); - TypeElement outTypeLattice = outValue.getType(); - TypeElement castTypeLattice = castType.toTypeElement(appView, inTypeLattice.nullability()); - - assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability()); - - if (inTypeLattice.lessThanOrEqual(castTypeLattice, appView)) { - // 1) Trivial cast. - // A a = ... - // A a' = (A) a; - // 2) Up-cast: we already have finer type info. - // A < B - // A a = ... - // B b = (B) a; - assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView); - // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast - // to the users of the CheckCast's out value. - // - // v2 = CheckCast A v1 ~~> DebugLocalWrite $v0 <- v1 - // - // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the - // CheckCast invalue users that includes the potential DebugLocalWrite. - removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue); - affectedValues.addAll(inValue.affectedValues()); - return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - } - - // If values of cast type are guaranteed to be null, then the out-value must be null if the cast - // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced - // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast - // type. - if (castType.isClassType() - && castType.isAlwaysNull(appViewWithLiveness) - && !outValue.hasDebugUsers()) { - // Replace all usages of the out-value by null. - it.previous(); - Value nullValue = it.insertConstNullInstruction(code, options); - it.next(); - checkCast.outValue().replaceUsers(nullValue); - affectedValues.addAll(nullValue.affectedValues()); - - // Replace the check-cast instruction by throwClassCastExceptionIfNotNull(). - UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod = - UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod( - appView, methodProcessor.getEventConsumer(), methodProcessingContext); - throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor); - InvokeStatic replacement = - InvokeStatic.builder() - .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod()) - .setSingleArgument(checkCast.object()) - .setPosition(checkCast) - .build(); - it.replaceCurrentInstruction(replacement); - assert replacement.lookupSingleTarget(appView, context) != null; - return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - } - - // If the cast is guaranteed to succeed and only there to ensure the program type checks, then - // check if the program would still type check after removing the cast. - if (checkCast.isSafeCheckCast() - || checkCast - .getFirstOperand() - .getDynamicType(appViewWithLiveness) - .getDynamicUpperBoundType() - .lessThanOrEqualUpToNullability(castTypeLattice, appView)) { - TypeElement useType = - TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue()); - if (inTypeLattice.lessThanOrEqualUpToNullability(useType, appView)) { - return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - } - } - - // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast: - // A < B < C - // c = ... // Even though we know c is of type A, - // a' = (B) c; // (this could be removed, since chained below.) - // a'' = (A) a'; // this should remain for runtime verification. - assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType()); - assert outTypeLattice.equalUpToNullability(castTypeLattice); - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - - // Returns true if the given instance-of instruction was removed. - private boolean removeInstanceOfInstructionIfTrivial( - AppView appViewWithLiveness, - InstanceOf instanceOf, - InstructionListIterator it, - IRCode code) { - ProgramMethod context = code.context(); - - // If the instance-of type is not accessible in the current context, we should not remove the - // instance-of instruction in order to preserve IllegalAccessError. - DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory); - if (instanceOfBaseType.isClassType()) { - DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType); - if (instanceOfClass == null - || AccessControl.isClassAccessible(instanceOfClass, context, appViewWithLiveness) - .isPossiblyFalse()) { - return false; - } - } - - Value inValue = instanceOf.value(); - if (!appView - .getOpenClosedInterfacesCollection() - .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { - return false; - } - - TypeElement inType = inValue.getType(); - TypeElement instanceOfType = - TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView); - Value aliasValue = inValue.getAliasedValue(); - - InstanceOfResult result = InstanceOfResult.UNKNOWN; - if (inType.isDefinitelyNull()) { - result = InstanceOfResult.FALSE; - } else if (inType.lessThanOrEqual(instanceOfType, appView) && !inType.isNullable()) { - result = InstanceOfResult.TRUE; - } else if (!aliasValue.isPhi() - && aliasValue.definition.isCreatingInstanceOrArray() - && instanceOfType.strictlyLessThan(inType, appView)) { - result = InstanceOfResult.FALSE; - } else if (appView.appInfo().hasLiveness()) { - if (instanceOf.type().isClassType() - && isNeverInstantiatedDirectlyOrIndirectly(instanceOf.type())) { - // The type of the instance-of instruction is a program class, and is never instantiated - // directly or indirectly. Thus, the in-value must be null, meaning that the instance-of - // instruction will always evaluate to false. - result = InstanceOfResult.FALSE; - } - - if (result == InstanceOfResult.UNKNOWN) { - if (inType.isClassType() - && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) { - // The type of the in-value is a program class, and is never instantiated directly or - // indirectly. This, the in-value must be null, meaning that the instance-of instruction - // will always evaluate to false. - result = InstanceOfResult.FALSE; - } - } - - if (result == InstanceOfResult.UNKNOWN) { - Value aliasedValue = - inValue.getSpecificAliasedValue( - value -> - value.isDefinedByInstructionSatisfying( - Instruction::isAssumeWithDynamicTypeAssumption)); - if (aliasedValue != null) { - DynamicTypeWithUpperBound dynamicType = - aliasedValue.getDefinition().asAssume().getDynamicTypeAssumption().getDynamicType(); - Nullability nullability = dynamicType.getNullability(); - if (nullability.isDefinitelyNull()) { - result = InstanceOfResult.FALSE; - } else if (dynamicType.getDynamicUpperBoundType().lessThanOrEqual(instanceOfType, appView) - && (!inType.isNullable() || !nullability.isNullable())) { - result = InstanceOfResult.TRUE; - } - } - } - } - if (result != InstanceOfResult.UNKNOWN) { - ConstNumber newInstruction = - new ConstNumber( - new Value( - code.valueNumberGenerator.next(), - TypeElement.getInt(), - instanceOf.outValue().getLocalInfo()), - result == InstanceOfResult.TRUE ? 1 : 0); - it.replaceCurrentInstruction(newInstruction); - return true; - } - return false; - } - - private boolean isNeverInstantiatedDirectlyOrIndirectly(DexType type) { - assert appView.appInfo().hasLiveness(); - assert type.isClassType(); - DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); - return clazz != null - && !appView.appInfo().withLiveness().isInstantiatedDirectlyOrIndirectly(clazz); - } -} 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 4ad6534ed0..0cfa945fcf 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 @@ -4,6 +4,7 @@ package com.android.tools.r8.ir.optimize; +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS; @@ -13,7 +14,9 @@ import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; +import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AccessControl; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DebugLocalInfo; @@ -22,12 +25,16 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; +import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.analysis.type.TypeUtils; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.code.ArrayLength; import com.android.tools.r8.ir.code.Assume; @@ -36,6 +43,7 @@ import com.android.tools.r8.ir.code.Binop; import com.android.tools.r8.ir.code.CatchHandlers; import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; +import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstClass; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; @@ -49,6 +57,7 @@ import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.InstanceFieldInstruction; import com.android.tools.r8.ir.code.InstanceGet; +import com.android.tools.r8.ir.code.InstanceOf; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; import com.android.tools.r8.ir.code.InstructionIterator; @@ -59,6 +68,7 @@ import com.android.tools.r8.ir.code.InvokeInterface; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; +import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.NewInstance; @@ -68,8 +78,11 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; +import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; @@ -102,6 +115,12 @@ public class CodeRewriter { + private enum InstanceOfResult { + UNKNOWN, + TRUE, + FALSE + } + // This constant was determined by experimentation. private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50; @@ -418,6 +437,333 @@ public boolean rewriteMoveResult(IRCode code) { return changed; } + enum RemoveCheckCastInstructionIfTrivialResult { + NO_REMOVALS, + REMOVED_CAST_DO_NARROW + } + + public void removeTrivialCheckCastAndInstanceOfInstructions( + IRCode code, + ProgramMethod context, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext) { + if (!appView.enableWholeProgramOptimizations()) { + return; + } + + assert appView.appInfo().hasLiveness(); + AppView appViewWithLiveness = appView.withLiveness(); + + if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) { + return; + } + + IRMetadata metadata = code.metadata(); + if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) { + return; + } + + // If we can remove a CheckCast it is due to us having at least as much information about the + // type as the CheckCast gives. We then need to propagate that information to the users of + // the CheckCast to ensure further optimizations and removals of CheckCast: + // + // : 1: NewArrayEmpty v2 <- v1(1) java.lang.String[] <-- v2 = String[] + // ... + // : 2: CheckCast v5 <- v2; java.lang.Object[] <-- v5 = Object[] + // ... + // : 3: ArrayGet v7 <- v5, v6(0) <-- v7 = Object + // : 4: CheckCast v8 <- v7; java.lang.String <-- v8 = String + // ... + // + // When looking at line 2 we can conclude that the CheckCast is trivial because v2 is String[] + // and remove it. However, v7 is still only known to be Object and we cannot remove the + // CheckCast at line 4 unless we update v7 with the most precise information by narrowing the + // affected values of v5. We therefore have to run the type analysis after each CheckCast + // removal. + TypeAnalysis typeAnalysis = new TypeAnalysis(appView); + Set affectedValues = Sets.newIdentityHashSet(); + InstructionListIterator it = code.instructionListIterator(); + boolean needToRemoveTrivialPhis = false; + while (it.hasNext()) { + Instruction current = it.next(); + if (current.isCheckCast()) { + boolean hasPhiUsers = current.outValue().hasPhiUsers(); + RemoveCheckCastInstructionIfTrivialResult removeResult = + removeCheckCastInstructionIfTrivial( + appViewWithLiveness, + current.asCheckCast(), + it, + code, + context, + affectedValues, + methodProcessor, + methodProcessingContext); + if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) { + assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + needToRemoveTrivialPhis |= hasPhiUsers; + typeAnalysis.narrowing(affectedValues); + affectedValues.clear(); + } + } else if (current.isInstanceOf()) { + boolean hasPhiUsers = current.outValue().hasPhiUsers(); + if (removeInstanceOfInstructionIfTrivial( + appViewWithLiveness, current.asInstanceOf(), it, code)) { + needToRemoveTrivialPhis |= hasPhiUsers; + } + } + } + // ... v1 + // ... + // v2 <- check-cast v1, T + // v3 <- phi(v1, v2) + // Removing check-cast may result in a trivial phi: + // v3 <- phi(v1, v1) + if (needToRemoveTrivialPhis) { + code.removeAllDeadAndTrivialPhis(affectedValues); + if (!affectedValues.isEmpty()) { + typeAnalysis.narrowing(affectedValues); + } + } + assert code.isConsistentSSA(appView); + } + + // Returns true if the given check-cast instruction was removed. + private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial( + AppView appViewWithLiveness, + CheckCast checkCast, + InstructionListIterator it, + IRCode code, + ProgramMethod context, + Set affectedValues, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext) { + Value inValue = checkCast.object(); + Value outValue = checkCast.outValue(); + DexType castType = checkCast.getType(); + DexType baseCastType = castType.toBaseType(dexItemFactory); + + // If the cast type is not accessible in the current context, we should not remove the cast + // in order to preserve runtime errors. Note that JVM and ART behave differently: see + // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}. + if (baseCastType.isClassType()) { + DexClass baseCastClass = appView.definitionFor(baseCastType); + if (baseCastClass == null + || AccessControl.isClassAccessible(baseCastClass, code.context(), appViewWithLiveness) + .isPossiblyFalse()) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + } + + if (!appView + .getOpenClosedInterfacesCollection() + .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + + // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast + // elimination may lead to verification errors. See b/123269162. + if (options.canHaveArtCheckCastVerifierBug()) { + if (inValue.getType().isNullType() + && castType.isArrayType() + && castType.toBaseType(dexItemFactory).isFloatType()) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + } + + // If casting to an array of an interface type elimination may lead to verification errors. + // See b/132420510 and b/223424356. + if (options.canHaveIncorrectJoinForArrayOfInterfacesBug()) { + if (castType.isArrayType()) { + DexType baseType = castType.toBaseType(dexItemFactory); + if (baseType.isClassType() && baseType.isInterface(appViewWithLiveness)) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + } + } + + TypeElement inTypeLattice = inValue.getType(); + TypeElement outTypeLattice = outValue.getType(); + TypeElement castTypeLattice = castType.toTypeElement(appView, inTypeLattice.nullability()); + + assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability()); + + if (inTypeLattice.lessThanOrEqual(castTypeLattice, appView)) { + // 1) Trivial cast. + // A a = ... + // A a' = (A) a; + // 2) Up-cast: we already have finer type info. + // A < B + // A a = ... + // B b = (B) a; + assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView); + // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast + // to the users of the CheckCast's out value. + // + // v2 = CheckCast A v1 ~~> DebugLocalWrite $v0 <- v1 + // + // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the + // CheckCast invalue users that includes the potential DebugLocalWrite. + removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue); + affectedValues.addAll(inValue.affectedValues()); + return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + } + + // If values of cast type are guaranteed to be null, then the out-value must be null if the cast + // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced + // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast + // type. + if (castType.isClassType() + && castType.isAlwaysNull(appViewWithLiveness) + && !outValue.hasDebugUsers()) { + // Replace all usages of the out-value by null. + it.previous(); + Value nullValue = it.insertConstNullInstruction(code, options); + it.next(); + checkCast.outValue().replaceUsers(nullValue); + affectedValues.addAll(nullValue.affectedValues()); + + // Replace the check-cast instruction by throwClassCastExceptionIfNotNull(). + UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod = + UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod( + appView, methodProcessor.getEventConsumer(), methodProcessingContext); + throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor); + InvokeStatic replacement = + InvokeStatic.builder() + .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod()) + .setSingleArgument(checkCast.object()) + .setPosition(checkCast) + .build(); + it.replaceCurrentInstruction(replacement); + assert replacement.lookupSingleTarget(appView, context) != null; + return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + } + + // If the cast is guaranteed to succeed and only there to ensure the program type checks, then + // check if the program would still type check after removing the cast. + if (checkCast.isSafeCheckCast() + || checkCast + .getFirstOperand() + .getDynamicType(appViewWithLiveness) + .getDynamicUpperBoundType() + .lessThanOrEqualUpToNullability(castTypeLattice, appView)) { + TypeElement useType = + TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue()); + if (inTypeLattice.lessThanOrEqualUpToNullability(useType, appView)) { + return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + } + } + + // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast: + // A < B < C + // c = ... // Even though we know c is of type A, + // a' = (B) c; // (this could be removed, since chained below.) + // a'' = (A) a'; // this should remain for runtime verification. + assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType()); + assert outTypeLattice.equalUpToNullability(castTypeLattice); + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + + // Returns true if the given instance-of instruction was removed. + private boolean removeInstanceOfInstructionIfTrivial( + AppView appViewWithLiveness, + InstanceOf instanceOf, + InstructionListIterator it, + IRCode code) { + ProgramMethod context = code.context(); + + // If the instance-of type is not accessible in the current context, we should not remove the + // instance-of instruction in order to preserve IllegalAccessError. + DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory); + if (instanceOfBaseType.isClassType()) { + DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType); + if (instanceOfClass == null + || AccessControl.isClassAccessible(instanceOfClass, context, appViewWithLiveness) + .isPossiblyFalse()) { + return false; + } + } + + Value inValue = instanceOf.value(); + if (!appView + .getOpenClosedInterfacesCollection() + .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { + return false; + } + + TypeElement inType = inValue.getType(); + TypeElement instanceOfType = + TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView); + Value aliasValue = inValue.getAliasedValue(); + + InstanceOfResult result = InstanceOfResult.UNKNOWN; + if (inType.isDefinitelyNull()) { + result = InstanceOfResult.FALSE; + } else if (inType.lessThanOrEqual(instanceOfType, appView) && !inType.isNullable()) { + result = InstanceOfResult.TRUE; + } else if (!aliasValue.isPhi() + && aliasValue.definition.isCreatingInstanceOrArray() + && instanceOfType.strictlyLessThan(inType, appView)) { + result = InstanceOfResult.FALSE; + } else if (appView.appInfo().hasLiveness()) { + if (instanceOf.type().isClassType() + && isNeverInstantiatedDirectlyOrIndirectly(instanceOf.type())) { + // The type of the instance-of instruction is a program class, and is never instantiated + // directly or indirectly. Thus, the in-value must be null, meaning that the instance-of + // instruction will always evaluate to false. + result = InstanceOfResult.FALSE; + } + + if (result == InstanceOfResult.UNKNOWN) { + if (inType.isClassType() + && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) { + // The type of the in-value is a program class, and is never instantiated directly or + // indirectly. This, the in-value must be null, meaning that the instance-of instruction + // will always evaluate to false. + result = InstanceOfResult.FALSE; + } + } + + if (result == InstanceOfResult.UNKNOWN) { + Value aliasedValue = + inValue.getSpecificAliasedValue( + value -> + value.isDefinedByInstructionSatisfying( + Instruction::isAssumeWithDynamicTypeAssumption)); + if (aliasedValue != null) { + DynamicTypeWithUpperBound dynamicType = + aliasedValue.getDefinition().asAssume().getDynamicTypeAssumption().getDynamicType(); + Nullability nullability = dynamicType.getNullability(); + if (nullability.isDefinitelyNull()) { + result = InstanceOfResult.FALSE; + } else if (dynamicType.getDynamicUpperBoundType().lessThanOrEqual(instanceOfType, appView) + && (!inType.isNullable() || !nullability.isNullable())) { + result = InstanceOfResult.TRUE; + } + } + } + } + if (result != InstanceOfResult.UNKNOWN) { + ConstNumber newInstruction = + new ConstNumber( + new Value( + code.valueNumberGenerator.next(), + TypeElement.getInt(), + instanceOf.outValue().getLocalInfo()), + result == InstanceOfResult.TRUE ? 1 : 0); + it.replaceCurrentInstruction(newInstruction); + return true; + } + return false; + } + + private boolean isNeverInstantiatedDirectlyOrIndirectly(DexType type) { + assert appView.appInfo().hasLiveness(); + assert type.isClassType(); + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); + return clazz != null + && !appView.appInfo().withLiveness().isInstantiatedDirectlyOrIndirectly(clazz); + } + public static void removeOrReplaceByDebugLocalWrite( Instruction currentInstruction, InstructionListIterator it, Value inValue, Value outValue) { if (outValue.hasLocalInfo() && outValue.getLocalInfo() != inValue.getLocalInfo()) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index 889a43448c..82df50071b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -20,7 +20,6 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; -import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; import com.android.tools.r8.ir.optimize.AssumeRemover; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.InliningOracle; @@ -247,8 +246,8 @@ public final void processMethodCode( // If a method was inlined we may be able to remove check-cast instructions because we may // have more information about the types of the arguments at the call site. This is // particularly important for bridge methods. - new TrivialCheckCastAndInstanceOfRemover(appView) - .run(code, method, methodProcessor, methodProcessingContext); + codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( + code, method, methodProcessor, methodProcessingContext); // If a method was inlined we may be able to prune additional branches. new BranchSimplifier(appView).simplifyBranches(code); // If a method was inlined we may see more trivial computation/conversion of String. From dcf1ac4b505ab3d759e0cc3e7f433544a694d86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gjesse?= Date: Thu, 8 Jun 2023 10:06:52 +0000 Subject: [PATCH 040/153] Revert "Move inner classes to correct top classes" This reverts commit 4f47f394830db30517c74b807c39b84a97e191eb. Reason for revert: Breaks the build. Change-Id: Ie395f4637c116d9e3c871ab31b10b419f2e4fa4f --- .../tools/r8/ir/conversion/IRConverter.java | 1 + .../conversion/passes/BranchSimplifier.java | 145 +---------------- .../tools/r8/ir/optimize/CodeRewriter.java | 146 ++++++++++++++++++ .../optimize/classinliner/ClassInliner.java | 2 + 4 files changed, 151 insertions(+), 143 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index fc1d15a557..5cc5c1c7fa 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -830,6 +830,7 @@ Timing optimize( assert options.inlinerOptions().enableInlining && inliner != null; classInliner.processMethodCode( appView.withLiveness(), + codeRewriter, stringOptimizer, enumValueOptimizer, code.context(), diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index 199df6244e..1cc873d002 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -20,7 +20,6 @@ import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.InstanceGet; @@ -35,6 +34,8 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.code.Xor; +import com.android.tools.r8.ir.optimize.CodeRewriter.IfBuilder; +import com.android.tools.r8.ir.optimize.CodeRewriter.SwitchBuilder; import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; @@ -43,13 +44,9 @@ import com.android.tools.r8.utils.LongInterval; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -1090,142 +1087,4 @@ public void convertSwitchToSwitchAndIfs( // Finally add the blocks. newBlocks.forEach(blocksIterator::add); } - - // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings. - private abstract static class InstructionBuilder { - - int blockNumber; - final Position position; - - InstructionBuilder(Position position) { - this.position = position; - } - - abstract T self(); - - T setBlockNumber(int blockNumber) { - this.blockNumber = blockNumber; - return self(); - } - } - - private static class SwitchBuilder extends InstructionBuilder { - - private Value value; - private final Int2ReferenceSortedMap keyToTarget = new Int2ReferenceAVLTreeMap<>(); - private BasicBlock fallthrough; - - SwitchBuilder(Position position) { - super(position); - } - - @Override - SwitchBuilder self() { - return this; - } - - SwitchBuilder setValue(Value value) { - this.value = value; - return this; - } - - SwitchBuilder addKeyAndTarget(int key, BasicBlock target) { - keyToTarget.put(key, target); - return this; - } - - SwitchBuilder setFallthrough(BasicBlock fallthrough) { - this.fallthrough = fallthrough; - return this; - } - - BasicBlock build(IRMetadata metadata) { - final int NOT_FOUND = -1; - Object2IntMap targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>(); - targetToSuccessorIndex.defaultReturnValue(NOT_FOUND); - - int[] keys = new int[keyToTarget.size()]; - int[] targetBlockIndices = new int[keyToTarget.size()]; - // Sort keys descending. - int count = 0; - IntIterator iter = keyToTarget.keySet().iterator(); - while (iter.hasNext()) { - int key = iter.nextInt(); - BasicBlock target = keyToTarget.get(key); - Integer targetIndex = - targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size()); - keys[count] = key; - targetBlockIndices[count] = targetIndex; - count++; - } - Integer fallthroughIndex = - targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size()); - IntSwitch newSwitch = new IntSwitch(value, keys, targetBlockIndices, fallthroughIndex); - newSwitch.setPosition(position); - BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch, metadata); - for (BasicBlock successor : targetToSuccessorIndex.keySet()) { - newSwitchBlock.link(successor); - } - return newSwitchBlock; - } - } - - private static class IfBuilder extends InstructionBuilder { - - private final IRCode code; - private Value left; - private int right; - private BasicBlock target; - private BasicBlock fallthrough; - - IfBuilder(Position position, IRCode code) { - super(position); - this.code = code; - } - - @Override - IfBuilder self() { - return this; - } - - IfBuilder setLeft(Value left) { - this.left = left; - return this; - } - - IfBuilder setRight(int right) { - this.right = right; - return this; - } - - IfBuilder setTarget(BasicBlock target) { - this.target = target; - return this; - } - - IfBuilder setFallthrough(BasicBlock fallthrough) { - this.fallthrough = fallthrough; - return this; - } - - BasicBlock build() { - assert target != null; - assert fallthrough != null; - If newIf; - BasicBlock ifBlock; - if (right != 0) { - ConstNumber rightConst = code.createIntConstant(right); - rightConst.setPosition(position); - newIf = new If(IfType.EQ, ImmutableList.of(left, rightConst.dest())); - ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata(), rightConst); - } else { - newIf = new If(IfType.EQ, left); - ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata()); - } - newIf.setPosition(position); - ifBlock.link(target); - ifBlock.link(fallthrough); - return ifBlock; - } - } } 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 0cfa945fcf..719a05aeed 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 @@ -53,6 +53,7 @@ import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.InstanceFieldInstruction; @@ -63,6 +64,7 @@ import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InstructionOrPhi; +import com.android.tools.r8.ir.code.IntSwitch; import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeInterface; @@ -91,13 +93,18 @@ import com.google.common.collect.Streams; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; +import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.ArrayList; @@ -332,6 +339,144 @@ public void rewriteThrowNullPointerException(IRCode code) { assert code.isConsistentSSA(appView); } + // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings. + public abstract static class InstructionBuilder { + + protected int blockNumber; + protected final Position position; + + protected InstructionBuilder(Position position) { + this.position = position; + } + + public abstract T self(); + + public T setBlockNumber(int blockNumber) { + this.blockNumber = blockNumber; + return self(); + } + } + + public static class SwitchBuilder extends InstructionBuilder { + + private Value value; + private final Int2ReferenceSortedMap keyToTarget = new Int2ReferenceAVLTreeMap<>(); + private BasicBlock fallthrough; + + public SwitchBuilder(Position position) { + super(position); + } + + @Override + public SwitchBuilder self() { + return this; + } + + public SwitchBuilder setValue(Value value) { + this.value = value; + return this; + } + + public SwitchBuilder addKeyAndTarget(int key, BasicBlock target) { + keyToTarget.put(key, target); + return this; + } + + public SwitchBuilder setFallthrough(BasicBlock fallthrough) { + this.fallthrough = fallthrough; + return this; + } + + public BasicBlock build(IRMetadata metadata) { + final int NOT_FOUND = -1; + Object2IntMap targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>(); + targetToSuccessorIndex.defaultReturnValue(NOT_FOUND); + + int[] keys = new int[keyToTarget.size()]; + int[] targetBlockIndices = new int[keyToTarget.size()]; + // Sort keys descending. + int count = 0; + IntIterator iter = keyToTarget.keySet().iterator(); + while (iter.hasNext()) { + int key = iter.nextInt(); + BasicBlock target = keyToTarget.get(key); + Integer targetIndex = + targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size()); + keys[count] = key; + targetBlockIndices[count] = targetIndex; + count++; + } + Integer fallthroughIndex = + targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size()); + IntSwitch newSwitch = new IntSwitch(value, keys, targetBlockIndices, fallthroughIndex); + newSwitch.setPosition(position); + BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch, metadata); + for (BasicBlock successor : targetToSuccessorIndex.keySet()) { + newSwitchBlock.link(successor); + } + return newSwitchBlock; + } + } + + public static class IfBuilder extends InstructionBuilder { + + private final IRCode code; + private Value left; + private int right; + private BasicBlock target; + private BasicBlock fallthrough; + + public IfBuilder(Position position, IRCode code) { + super(position); + this.code = code; + } + + @Override + public IfBuilder self() { + return this; + } + + public IfBuilder setLeft(Value left) { + this.left = left; + return this; + } + + public IfBuilder setRight(int right) { + this.right = right; + return this; + } + + public IfBuilder setTarget(BasicBlock target) { + this.target = target; + return this; + } + + public IfBuilder setFallthrough(BasicBlock fallthrough) { + this.fallthrough = fallthrough; + return this; + } + + public BasicBlock build() { + assert target != null; + assert fallthrough != null; + If newIf; + BasicBlock ifBlock; + if (right != 0) { + ConstNumber rightConst = code.createIntConstant(right); + rightConst.setPosition(position); + newIf = new If(IfType.EQ, ImmutableList.of(left, rightConst.dest())); + ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata(), rightConst); + } else { + newIf = new If(IfType.EQ, left); + ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata()); + } + newIf.setPosition(position); + ifBlock.link(target); + ifBlock.link(fallthrough); + return ifBlock; + } + } + private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) { // TODO(sgjesse): Insert cast if required. TypeElement returnType = @@ -1827,6 +1972,7 @@ public void optimizeAlwaysThrowingInstructions(IRCode code) { assert code.isConsistentSSA(appView); } + private void insertNotNullCheck( BasicBlock block, InstructionListIterator iterator, diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index 82df50071b..435b01ca13 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -21,6 +21,7 @@ import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.ir.optimize.AssumeRemover; +import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.InliningOracle; import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException; @@ -126,6 +127,7 @@ enum EligibilityStatus { // public final void processMethodCode( AppView appView, + CodeRewriter codeRewriter, StringOptimizer stringOptimizer, EnumValueOptimizer enumValueOptimizer, ProgramMethod method, From 55ea53eae62568ff724875158623616302c1c5ac Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Thu, 8 Jun 2023 10:20:47 +0200 Subject: [PATCH 041/153] Don't run WhileOpTest by default This is taking more than 1 CPU hour running on art! Bug: b/286215385 Change-Id: I7dd27788c14a1b1facfd084ef2703032298b509c --- .../jdktests/Jdk11StreamAbstractTests.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java index 5822d2ff10..9ff2346aba 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java @@ -115,12 +115,17 @@ private static Path[] getJdk11StreamTestFiles() throws Exception { public static final String[] SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7 = new String[] { // Require the virtual method isDefault() in class java/lang/reflect/Method. - "org/openjdk/tests/java/util/stream/WhileOpTest.java", "org/openjdk/tests/java/util/stream/WhileOpStatefulTest.java", // Require a Random method not present before Android 7 and not desugared. "org/openjdk/tests/java/util/stream/IntPrimitiveOpsTests.java" }; + public static final String[] LONG_RUNNING_SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7 = + new String[] { + // Require the virtual method isDefault() in class java/lang/reflect/Method. + "org/openjdk/tests/java/util/stream/WhileOpTest.java", + }; + // Disabled because time to run > 1 min for each test. // Can be used for experimentation/testing purposes. private static String[] LONG_RUNNING_TESTS = From fc6dc07361d13f5b5f198e5494475624b389b6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 8 Jun 2023 12:45:47 +0200 Subject: [PATCH 042/153] Reland "Split TrivialCheckCastAndInstanceOfRemoval" This reverts commit e5be3448330016bb6006271207ee0290e21f8cec. Reland "Move inner classes to correct top classes" This reverts commit dcf1ac4b505ab3d759e0cc3e7f433544a694d86e. Change-Id: I5e905b7ffe2b6f35b36e06c936422c7fce67a0e7 --- .../tools/r8/ir/conversion/IRConverter.java | 10 +- .../conversion/passes/BranchSimplifier.java | 145 +++++- .../TrivialCheckCastAndInstanceOfRemover.java | 384 ++++++++++++++ .../tools/r8/ir/optimize/CodeRewriter.java | 492 ------------------ .../optimize/classinliner/ClassInliner.java | 7 +- 5 files changed, 535 insertions(+), 503 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 5cc5c1c7fa..55c6a062fa 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -33,6 +33,7 @@ import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; +import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer; @@ -729,8 +730,8 @@ Timing optimize( assert code.verifyTypes(appView); timing.begin("Remove trivial type checks/casts"); - codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( - code, context, methodProcessor, methodProcessingContext); + new TrivialCheckCastAndInstanceOfRemover(appView) + .run(code, context, methodProcessor, methodProcessingContext); timing.end(); if (enumValueOptimizer != null) { @@ -774,8 +775,8 @@ Timing optimize( timing.begin("Simplify control flow"); if (new BranchSimplifier(appView).simplifyBranches(code)) { timing.begin("Remove trivial type checks/casts"); - codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( - code, context, methodProcessor, methodProcessingContext); + new TrivialCheckCastAndInstanceOfRemover(appView) + .run(code, context, methodProcessor, methodProcessingContext); timing.end(); } timing.end(); @@ -830,7 +831,6 @@ Timing optimize( assert options.inlinerOptions().enableInlining && inliner != null; classInliner.processMethodCode( appView.withLiveness(), - codeRewriter, stringOptimizer, enumValueOptimizer, code.context(), diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index 1cc873d002..199df6244e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -20,6 +20,7 @@ import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.InstanceGet; @@ -34,8 +35,6 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.code.Xor; -import com.android.tools.r8.ir.optimize.CodeRewriter.IfBuilder; -import com.android.tools.r8.ir.optimize.CodeRewriter.SwitchBuilder; import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; @@ -44,9 +43,13 @@ import com.android.tools.r8.utils.LongInterval; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -1087,4 +1090,142 @@ public void convertSwitchToSwitchAndIfs( // Finally add the blocks. newBlocks.forEach(blocksIterator::add); } + + // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings. + private abstract static class InstructionBuilder { + + int blockNumber; + final Position position; + + InstructionBuilder(Position position) { + this.position = position; + } + + abstract T self(); + + T setBlockNumber(int blockNumber) { + this.blockNumber = blockNumber; + return self(); + } + } + + private static class SwitchBuilder extends InstructionBuilder { + + private Value value; + private final Int2ReferenceSortedMap keyToTarget = new Int2ReferenceAVLTreeMap<>(); + private BasicBlock fallthrough; + + SwitchBuilder(Position position) { + super(position); + } + + @Override + SwitchBuilder self() { + return this; + } + + SwitchBuilder setValue(Value value) { + this.value = value; + return this; + } + + SwitchBuilder addKeyAndTarget(int key, BasicBlock target) { + keyToTarget.put(key, target); + return this; + } + + SwitchBuilder setFallthrough(BasicBlock fallthrough) { + this.fallthrough = fallthrough; + return this; + } + + BasicBlock build(IRMetadata metadata) { + final int NOT_FOUND = -1; + Object2IntMap targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>(); + targetToSuccessorIndex.defaultReturnValue(NOT_FOUND); + + int[] keys = new int[keyToTarget.size()]; + int[] targetBlockIndices = new int[keyToTarget.size()]; + // Sort keys descending. + int count = 0; + IntIterator iter = keyToTarget.keySet().iterator(); + while (iter.hasNext()) { + int key = iter.nextInt(); + BasicBlock target = keyToTarget.get(key); + Integer targetIndex = + targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size()); + keys[count] = key; + targetBlockIndices[count] = targetIndex; + count++; + } + Integer fallthroughIndex = + targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size()); + IntSwitch newSwitch = new IntSwitch(value, keys, targetBlockIndices, fallthroughIndex); + newSwitch.setPosition(position); + BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch, metadata); + for (BasicBlock successor : targetToSuccessorIndex.keySet()) { + newSwitchBlock.link(successor); + } + return newSwitchBlock; + } + } + + private static class IfBuilder extends InstructionBuilder { + + private final IRCode code; + private Value left; + private int right; + private BasicBlock target; + private BasicBlock fallthrough; + + IfBuilder(Position position, IRCode code) { + super(position); + this.code = code; + } + + @Override + IfBuilder self() { + return this; + } + + IfBuilder setLeft(Value left) { + this.left = left; + return this; + } + + IfBuilder setRight(int right) { + this.right = right; + return this; + } + + IfBuilder setTarget(BasicBlock target) { + this.target = target; + return this; + } + + IfBuilder setFallthrough(BasicBlock fallthrough) { + this.fallthrough = fallthrough; + return this; + } + + BasicBlock build() { + assert target != null; + assert fallthrough != null; + If newIf; + BasicBlock ifBlock; + if (right != 0) { + ConstNumber rightConst = code.createIntConstant(right); + rightConst.setPosition(position); + newIf = new If(IfType.EQ, ImmutableList.of(left, rightConst.dest())); + ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata(), rightConst); + } else { + newIf = new If(IfType.EQ, left); + ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata()); + } + newIf.setPosition(position); + ifBlock.link(target); + ifBlock.link(fallthrough); + return ifBlock; + } + } } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java new file mode 100644 index 0000000000..e19b9633dd --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java @@ -0,0 +1,384 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; + +import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; +import com.android.tools.r8.graph.AccessControl; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; +import com.android.tools.r8.ir.analysis.type.Nullability; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; +import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.analysis.type.TypeUtils; +import com.android.tools.r8.ir.code.CheckCast; +import com.android.tools.r8.ir.code.ConstNumber; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.IRMetadata; +import com.android.tools.r8.ir.code.InstanceOf; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeStatic; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.optimize.CodeRewriter; +import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations; +import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.InternalOptions; +import com.google.common.collect.Sets; +import java.util.Set; + +public class TrivialCheckCastAndInstanceOfRemover { + + private final AppView appView; + private final InternalOptions options; + private final DexItemFactory dexItemFactory; + + public TrivialCheckCastAndInstanceOfRemover(AppView appView) { + this.appView = appView; + this.options = appView.options(); + this.dexItemFactory = appView.dexItemFactory(); + } + + public void run( + IRCode code, + ProgramMethod context, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext) { + if (!appView.enableWholeProgramOptimizations()) { + return; + } + + assert appView.appInfo().hasLiveness(); + AppView appViewWithLiveness = appView.withLiveness(); + + if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) { + return; + } + + IRMetadata metadata = code.metadata(); + if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) { + return; + } + + // If we can remove a CheckCast it is due to us having at least as much information about the + // type as the CheckCast gives. We then need to propagate that information to the users of + // the CheckCast to ensure further optimizations and removals of CheckCast: + // + // : 1: NewArrayEmpty v2 <- v1(1) java.lang.String[] <-- v2 = String[] + // ... + // : 2: CheckCast v5 <- v2; java.lang.Object[] <-- v5 = Object[] + // ... + // : 3: ArrayGet v7 <- v5, v6(0) <-- v7 = Object + // : 4: CheckCast v8 <- v7; java.lang.String <-- v8 = String + // ... + // + // When looking at line 2 we can conclude that the CheckCast is trivial because v2 is String[] + // and remove it. However, v7 is still only known to be Object and we cannot remove the + // CheckCast at line 4 unless we update v7 with the most precise information by narrowing the + // affected values of v5. We therefore have to run the type analysis after each CheckCast + // removal. + TypeAnalysis typeAnalysis = new TypeAnalysis(appView); + Set affectedValues = Sets.newIdentityHashSet(); + InstructionListIterator it = code.instructionListIterator(); + boolean needToRemoveTrivialPhis = false; + while (it.hasNext()) { + Instruction current = it.next(); + if (current.isCheckCast()) { + boolean hasPhiUsers = current.outValue().hasPhiUsers(); + RemoveCheckCastInstructionIfTrivialResult removeResult = + removeCheckCastInstructionIfTrivial( + appViewWithLiveness, + current.asCheckCast(), + it, + code, + context, + affectedValues, + methodProcessor, + methodProcessingContext); + if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) { + assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + needToRemoveTrivialPhis |= hasPhiUsers; + typeAnalysis.narrowing(affectedValues); + affectedValues.clear(); + } + } else if (current.isInstanceOf()) { + boolean hasPhiUsers = current.outValue().hasPhiUsers(); + if (removeInstanceOfInstructionIfTrivial( + appViewWithLiveness, current.asInstanceOf(), it, code)) { + needToRemoveTrivialPhis |= hasPhiUsers; + } + } + } + // ... v1 + // ... + // v2 <- check-cast v1, T + // v3 <- phi(v1, v2) + // Removing check-cast may result in a trivial phi: + // v3 <- phi(v1, v1) + if (needToRemoveTrivialPhis) { + code.removeAllDeadAndTrivialPhis(affectedValues); + if (!affectedValues.isEmpty()) { + typeAnalysis.narrowing(affectedValues); + } + } + assert code.isConsistentSSA(appView); + } + + enum RemoveCheckCastInstructionIfTrivialResult { + NO_REMOVALS, + REMOVED_CAST_DO_NARROW + } + + private enum InstanceOfResult { + UNKNOWN, + TRUE, + FALSE + } + + // Returns true if the given check-cast instruction was removed. + private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial( + AppView appViewWithLiveness, + CheckCast checkCast, + InstructionListIterator it, + IRCode code, + ProgramMethod context, + Set affectedValues, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext) { + Value inValue = checkCast.object(); + Value outValue = checkCast.outValue(); + DexType castType = checkCast.getType(); + DexType baseCastType = castType.toBaseType(dexItemFactory); + + // If the cast type is not accessible in the current context, we should not remove the cast + // in order to preserve runtime errors. Note that JVM and ART behave differently: see + // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}. + if (baseCastType.isClassType()) { + DexClass baseCastClass = appView.definitionFor(baseCastType); + if (baseCastClass == null + || AccessControl.isClassAccessible(baseCastClass, code.context(), appViewWithLiveness) + .isPossiblyFalse()) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + } + + if (!appView + .getOpenClosedInterfacesCollection() + .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + + // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast + // elimination may lead to verification errors. See b/123269162. + if (options.canHaveArtCheckCastVerifierBug()) { + if (inValue.getType().isNullType() + && castType.isArrayType() + && castType.toBaseType(dexItemFactory).isFloatType()) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + } + + // If casting to an array of an interface type elimination may lead to verification errors. + // See b/132420510 and b/223424356. + if (options.canHaveIncorrectJoinForArrayOfInterfacesBug()) { + if (castType.isArrayType()) { + DexType baseType = castType.toBaseType(dexItemFactory); + if (baseType.isClassType() && baseType.isInterface(appViewWithLiveness)) { + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + } + } + + TypeElement inTypeLattice = inValue.getType(); + TypeElement outTypeLattice = outValue.getType(); + TypeElement castTypeLattice = castType.toTypeElement(appView, inTypeLattice.nullability()); + + assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability()); + + if (inTypeLattice.lessThanOrEqual(castTypeLattice, appView)) { + // 1) Trivial cast. + // A a = ... + // A a' = (A) a; + // 2) Up-cast: we already have finer type info. + // A < B + // A a = ... + // B b = (B) a; + assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView); + // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast + // to the users of the CheckCast's out value. + // + // v2 = CheckCast A v1 ~~> DebugLocalWrite $v0 <- v1 + // + // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the + // CheckCast invalue users that includes the potential DebugLocalWrite. + CodeRewriter.removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue); + affectedValues.addAll(inValue.affectedValues()); + return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + } + + // If values of cast type are guaranteed to be null, then the out-value must be null if the cast + // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced + // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast + // type. + if (castType.isClassType() + && castType.isAlwaysNull(appViewWithLiveness) + && !outValue.hasDebugUsers()) { + // Replace all usages of the out-value by null. + it.previous(); + Value nullValue = it.insertConstNullInstruction(code, options); + it.next(); + checkCast.outValue().replaceUsers(nullValue); + affectedValues.addAll(nullValue.affectedValues()); + + // Replace the check-cast instruction by throwClassCastExceptionIfNotNull(). + UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod = + UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod( + appView, methodProcessor.getEventConsumer(), methodProcessingContext); + throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor); + InvokeStatic replacement = + InvokeStatic.builder() + .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod()) + .setSingleArgument(checkCast.object()) + .setPosition(checkCast) + .build(); + it.replaceCurrentInstruction(replacement); + assert replacement.lookupSingleTarget(appView, context) != null; + return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + } + + // If the cast is guaranteed to succeed and only there to ensure the program type checks, then + // check if the program would still type check after removing the cast. + if (checkCast.isSafeCheckCast() + || checkCast + .getFirstOperand() + .getDynamicType(appViewWithLiveness) + .getDynamicUpperBoundType() + .lessThanOrEqualUpToNullability(castTypeLattice, appView)) { + TypeElement useType = + TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue()); + if (inTypeLattice.lessThanOrEqualUpToNullability(useType, appView)) { + return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; + } + } + + // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast: + // A < B < C + // c = ... // Even though we know c is of type A, + // a' = (B) c; // (this could be removed, since chained below.) + // a'' = (A) a'; // this should remain for runtime verification. + assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType()); + assert outTypeLattice.equalUpToNullability(castTypeLattice); + return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; + } + + // Returns true if the given instance-of instruction was removed. + private boolean removeInstanceOfInstructionIfTrivial( + AppView appViewWithLiveness, + InstanceOf instanceOf, + InstructionListIterator it, + IRCode code) { + ProgramMethod context = code.context(); + + // If the instance-of type is not accessible in the current context, we should not remove the + // instance-of instruction in order to preserve IllegalAccessError. + DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory); + if (instanceOfBaseType.isClassType()) { + DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType); + if (instanceOfClass == null + || AccessControl.isClassAccessible(instanceOfClass, context, appViewWithLiveness) + .isPossiblyFalse()) { + return false; + } + } + + Value inValue = instanceOf.value(); + if (!appView + .getOpenClosedInterfacesCollection() + .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { + return false; + } + + TypeElement inType = inValue.getType(); + TypeElement instanceOfType = + TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView); + Value aliasValue = inValue.getAliasedValue(); + + InstanceOfResult result = InstanceOfResult.UNKNOWN; + if (inType.isDefinitelyNull()) { + result = InstanceOfResult.FALSE; + } else if (inType.lessThanOrEqual(instanceOfType, appView) && !inType.isNullable()) { + result = InstanceOfResult.TRUE; + } else if (!aliasValue.isPhi() + && aliasValue.definition.isCreatingInstanceOrArray() + && instanceOfType.strictlyLessThan(inType, appView)) { + result = InstanceOfResult.FALSE; + } else if (appView.appInfo().hasLiveness()) { + if (instanceOf.type().isClassType() + && isNeverInstantiatedDirectlyOrIndirectly(instanceOf.type())) { + // The type of the instance-of instruction is a program class, and is never instantiated + // directly or indirectly. Thus, the in-value must be null, meaning that the instance-of + // instruction will always evaluate to false. + result = InstanceOfResult.FALSE; + } + + if (result == InstanceOfResult.UNKNOWN) { + if (inType.isClassType() + && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) { + // The type of the in-value is a program class, and is never instantiated directly or + // indirectly. This, the in-value must be null, meaning that the instance-of instruction + // will always evaluate to false. + result = InstanceOfResult.FALSE; + } + } + + if (result == InstanceOfResult.UNKNOWN) { + Value aliasedValue = + inValue.getSpecificAliasedValue( + value -> + value.isDefinedByInstructionSatisfying( + Instruction::isAssumeWithDynamicTypeAssumption)); + if (aliasedValue != null) { + DynamicTypeWithUpperBound dynamicType = + aliasedValue.getDefinition().asAssume().getDynamicTypeAssumption().getDynamicType(); + Nullability nullability = dynamicType.getNullability(); + if (nullability.isDefinitelyNull()) { + result = InstanceOfResult.FALSE; + } else if (dynamicType.getDynamicUpperBoundType().lessThanOrEqual(instanceOfType, appView) + && (!inType.isNullable() || !nullability.isNullable())) { + result = InstanceOfResult.TRUE; + } + } + } + } + if (result != InstanceOfResult.UNKNOWN) { + ConstNumber newInstruction = + new ConstNumber( + new Value( + code.valueNumberGenerator.next(), + TypeElement.getInt(), + instanceOf.outValue().getLocalInfo()), + result == InstanceOfResult.TRUE ? 1 : 0); + it.replaceCurrentInstruction(newInstruction); + return true; + } + return false; + } + + private boolean isNeverInstantiatedDirectlyOrIndirectly(DexType type) { + assert appView.appInfo().hasLiveness(); + assert type.isClassType(); + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); + return clazz != null + && !appView.appInfo().withLiveness().isInstantiatedDirectlyOrIndirectly(clazz); + } +} 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 719a05aeed..4ad6534ed0 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 @@ -4,7 +4,6 @@ package com.android.tools.r8.ir.optimize; -import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS; @@ -14,9 +13,7 @@ import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; -import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.graph.AccessControl; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DebugLocalInfo; @@ -25,16 +22,12 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; -import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; -import com.android.tools.r8.ir.analysis.type.TypeUtils; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.code.ArrayLength; import com.android.tools.r8.ir.code.Assume; @@ -43,7 +36,6 @@ import com.android.tools.r8.ir.code.Binop; import com.android.tools.r8.ir.code.CatchHandlers; import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; -import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstClass; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; @@ -53,24 +45,20 @@ import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.InstanceFieldInstruction; import com.android.tools.r8.ir.code.InstanceGet; -import com.android.tools.r8.ir.code.InstanceOf; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InstructionOrPhi; -import com.android.tools.r8.ir.code.IntSwitch; import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeInterface; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; -import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.NewInstance; @@ -80,11 +68,8 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.conversion.MethodProcessor; -import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; -import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; @@ -93,18 +78,13 @@ import com.google.common.collect.Streams; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; -import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.ArrayList; @@ -122,12 +102,6 @@ public class CodeRewriter { - private enum InstanceOfResult { - UNKNOWN, - TRUE, - FALSE - } - // This constant was determined by experimentation. private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50; @@ -339,144 +313,6 @@ public void rewriteThrowNullPointerException(IRCode code) { assert code.isConsistentSSA(appView); } - // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings. - public abstract static class InstructionBuilder { - - protected int blockNumber; - protected final Position position; - - protected InstructionBuilder(Position position) { - this.position = position; - } - - public abstract T self(); - - public T setBlockNumber(int blockNumber) { - this.blockNumber = blockNumber; - return self(); - } - } - - public static class SwitchBuilder extends InstructionBuilder { - - private Value value; - private final Int2ReferenceSortedMap keyToTarget = new Int2ReferenceAVLTreeMap<>(); - private BasicBlock fallthrough; - - public SwitchBuilder(Position position) { - super(position); - } - - @Override - public SwitchBuilder self() { - return this; - } - - public SwitchBuilder setValue(Value value) { - this.value = value; - return this; - } - - public SwitchBuilder addKeyAndTarget(int key, BasicBlock target) { - keyToTarget.put(key, target); - return this; - } - - public SwitchBuilder setFallthrough(BasicBlock fallthrough) { - this.fallthrough = fallthrough; - return this; - } - - public BasicBlock build(IRMetadata metadata) { - final int NOT_FOUND = -1; - Object2IntMap targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>(); - targetToSuccessorIndex.defaultReturnValue(NOT_FOUND); - - int[] keys = new int[keyToTarget.size()]; - int[] targetBlockIndices = new int[keyToTarget.size()]; - // Sort keys descending. - int count = 0; - IntIterator iter = keyToTarget.keySet().iterator(); - while (iter.hasNext()) { - int key = iter.nextInt(); - BasicBlock target = keyToTarget.get(key); - Integer targetIndex = - targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size()); - keys[count] = key; - targetBlockIndices[count] = targetIndex; - count++; - } - Integer fallthroughIndex = - targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size()); - IntSwitch newSwitch = new IntSwitch(value, keys, targetBlockIndices, fallthroughIndex); - newSwitch.setPosition(position); - BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch, metadata); - for (BasicBlock successor : targetToSuccessorIndex.keySet()) { - newSwitchBlock.link(successor); - } - return newSwitchBlock; - } - } - - public static class IfBuilder extends InstructionBuilder { - - private final IRCode code; - private Value left; - private int right; - private BasicBlock target; - private BasicBlock fallthrough; - - public IfBuilder(Position position, IRCode code) { - super(position); - this.code = code; - } - - @Override - public IfBuilder self() { - return this; - } - - public IfBuilder setLeft(Value left) { - this.left = left; - return this; - } - - public IfBuilder setRight(int right) { - this.right = right; - return this; - } - - public IfBuilder setTarget(BasicBlock target) { - this.target = target; - return this; - } - - public IfBuilder setFallthrough(BasicBlock fallthrough) { - this.fallthrough = fallthrough; - return this; - } - - public BasicBlock build() { - assert target != null; - assert fallthrough != null; - If newIf; - BasicBlock ifBlock; - if (right != 0) { - ConstNumber rightConst = code.createIntConstant(right); - rightConst.setPosition(position); - newIf = new If(IfType.EQ, ImmutableList.of(left, rightConst.dest())); - ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata(), rightConst); - } else { - newIf = new If(IfType.EQ, left); - ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata()); - } - newIf.setPosition(position); - ifBlock.link(target); - ifBlock.link(fallthrough); - return ifBlock; - } - } - private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) { // TODO(sgjesse): Insert cast if required. TypeElement returnType = @@ -582,333 +418,6 @@ public boolean rewriteMoveResult(IRCode code) { return changed; } - enum RemoveCheckCastInstructionIfTrivialResult { - NO_REMOVALS, - REMOVED_CAST_DO_NARROW - } - - public void removeTrivialCheckCastAndInstanceOfInstructions( - IRCode code, - ProgramMethod context, - MethodProcessor methodProcessor, - MethodProcessingContext methodProcessingContext) { - if (!appView.enableWholeProgramOptimizations()) { - return; - } - - assert appView.appInfo().hasLiveness(); - AppView appViewWithLiveness = appView.withLiveness(); - - if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) { - return; - } - - IRMetadata metadata = code.metadata(); - if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) { - return; - } - - // If we can remove a CheckCast it is due to us having at least as much information about the - // type as the CheckCast gives. We then need to propagate that information to the users of - // the CheckCast to ensure further optimizations and removals of CheckCast: - // - // : 1: NewArrayEmpty v2 <- v1(1) java.lang.String[] <-- v2 = String[] - // ... - // : 2: CheckCast v5 <- v2; java.lang.Object[] <-- v5 = Object[] - // ... - // : 3: ArrayGet v7 <- v5, v6(0) <-- v7 = Object - // : 4: CheckCast v8 <- v7; java.lang.String <-- v8 = String - // ... - // - // When looking at line 2 we can conclude that the CheckCast is trivial because v2 is String[] - // and remove it. However, v7 is still only known to be Object and we cannot remove the - // CheckCast at line 4 unless we update v7 with the most precise information by narrowing the - // affected values of v5. We therefore have to run the type analysis after each CheckCast - // removal. - TypeAnalysis typeAnalysis = new TypeAnalysis(appView); - Set affectedValues = Sets.newIdentityHashSet(); - InstructionListIterator it = code.instructionListIterator(); - boolean needToRemoveTrivialPhis = false; - while (it.hasNext()) { - Instruction current = it.next(); - if (current.isCheckCast()) { - boolean hasPhiUsers = current.outValue().hasPhiUsers(); - RemoveCheckCastInstructionIfTrivialResult removeResult = - removeCheckCastInstructionIfTrivial( - appViewWithLiveness, - current.asCheckCast(), - it, - code, - context, - affectedValues, - methodProcessor, - methodProcessingContext); - if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) { - assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - needToRemoveTrivialPhis |= hasPhiUsers; - typeAnalysis.narrowing(affectedValues); - affectedValues.clear(); - } - } else if (current.isInstanceOf()) { - boolean hasPhiUsers = current.outValue().hasPhiUsers(); - if (removeInstanceOfInstructionIfTrivial( - appViewWithLiveness, current.asInstanceOf(), it, code)) { - needToRemoveTrivialPhis |= hasPhiUsers; - } - } - } - // ... v1 - // ... - // v2 <- check-cast v1, T - // v3 <- phi(v1, v2) - // Removing check-cast may result in a trivial phi: - // v3 <- phi(v1, v1) - if (needToRemoveTrivialPhis) { - code.removeAllDeadAndTrivialPhis(affectedValues); - if (!affectedValues.isEmpty()) { - typeAnalysis.narrowing(affectedValues); - } - } - assert code.isConsistentSSA(appView); - } - - // Returns true if the given check-cast instruction was removed. - private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial( - AppView appViewWithLiveness, - CheckCast checkCast, - InstructionListIterator it, - IRCode code, - ProgramMethod context, - Set affectedValues, - MethodProcessor methodProcessor, - MethodProcessingContext methodProcessingContext) { - Value inValue = checkCast.object(); - Value outValue = checkCast.outValue(); - DexType castType = checkCast.getType(); - DexType baseCastType = castType.toBaseType(dexItemFactory); - - // If the cast type is not accessible in the current context, we should not remove the cast - // in order to preserve runtime errors. Note that JVM and ART behave differently: see - // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}. - if (baseCastType.isClassType()) { - DexClass baseCastClass = appView.definitionFor(baseCastType); - if (baseCastClass == null - || AccessControl.isClassAccessible(baseCastClass, code.context(), appViewWithLiveness) - .isPossiblyFalse()) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - } - - if (!appView - .getOpenClosedInterfacesCollection() - .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - - // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast - // elimination may lead to verification errors. See b/123269162. - if (options.canHaveArtCheckCastVerifierBug()) { - if (inValue.getType().isNullType() - && castType.isArrayType() - && castType.toBaseType(dexItemFactory).isFloatType()) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - } - - // If casting to an array of an interface type elimination may lead to verification errors. - // See b/132420510 and b/223424356. - if (options.canHaveIncorrectJoinForArrayOfInterfacesBug()) { - if (castType.isArrayType()) { - DexType baseType = castType.toBaseType(dexItemFactory); - if (baseType.isClassType() && baseType.isInterface(appViewWithLiveness)) { - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - } - } - - TypeElement inTypeLattice = inValue.getType(); - TypeElement outTypeLattice = outValue.getType(); - TypeElement castTypeLattice = castType.toTypeElement(appView, inTypeLattice.nullability()); - - assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability()); - - if (inTypeLattice.lessThanOrEqual(castTypeLattice, appView)) { - // 1) Trivial cast. - // A a = ... - // A a' = (A) a; - // 2) Up-cast: we already have finer type info. - // A < B - // A a = ... - // B b = (B) a; - assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView); - // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast - // to the users of the CheckCast's out value. - // - // v2 = CheckCast A v1 ~~> DebugLocalWrite $v0 <- v1 - // - // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the - // CheckCast invalue users that includes the potential DebugLocalWrite. - removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue); - affectedValues.addAll(inValue.affectedValues()); - return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - } - - // If values of cast type are guaranteed to be null, then the out-value must be null if the cast - // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced - // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast - // type. - if (castType.isClassType() - && castType.isAlwaysNull(appViewWithLiveness) - && !outValue.hasDebugUsers()) { - // Replace all usages of the out-value by null. - it.previous(); - Value nullValue = it.insertConstNullInstruction(code, options); - it.next(); - checkCast.outValue().replaceUsers(nullValue); - affectedValues.addAll(nullValue.affectedValues()); - - // Replace the check-cast instruction by throwClassCastExceptionIfNotNull(). - UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod = - UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod( - appView, methodProcessor.getEventConsumer(), methodProcessingContext); - throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor); - InvokeStatic replacement = - InvokeStatic.builder() - .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod()) - .setSingleArgument(checkCast.object()) - .setPosition(checkCast) - .build(); - it.replaceCurrentInstruction(replacement); - assert replacement.lookupSingleTarget(appView, context) != null; - return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - } - - // If the cast is guaranteed to succeed and only there to ensure the program type checks, then - // check if the program would still type check after removing the cast. - if (checkCast.isSafeCheckCast() - || checkCast - .getFirstOperand() - .getDynamicType(appViewWithLiveness) - .getDynamicUpperBoundType() - .lessThanOrEqualUpToNullability(castTypeLattice, appView)) { - TypeElement useType = - TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue()); - if (inTypeLattice.lessThanOrEqualUpToNullability(useType, appView)) { - return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW; - } - } - - // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast: - // A < B < C - // c = ... // Even though we know c is of type A, - // a' = (B) c; // (this could be removed, since chained below.) - // a'' = (A) a'; // this should remain for runtime verification. - assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType()); - assert outTypeLattice.equalUpToNullability(castTypeLattice); - return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS; - } - - // Returns true if the given instance-of instruction was removed. - private boolean removeInstanceOfInstructionIfTrivial( - AppView appViewWithLiveness, - InstanceOf instanceOf, - InstructionListIterator it, - IRCode code) { - ProgramMethod context = code.context(); - - // If the instance-of type is not accessible in the current context, we should not remove the - // instance-of instruction in order to preserve IllegalAccessError. - DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory); - if (instanceOfBaseType.isClassType()) { - DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType); - if (instanceOfClass == null - || AccessControl.isClassAccessible(instanceOfClass, context, appViewWithLiveness) - .isPossiblyFalse()) { - return false; - } - } - - Value inValue = instanceOf.value(); - if (!appView - .getOpenClosedInterfacesCollection() - .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) { - return false; - } - - TypeElement inType = inValue.getType(); - TypeElement instanceOfType = - TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView); - Value aliasValue = inValue.getAliasedValue(); - - InstanceOfResult result = InstanceOfResult.UNKNOWN; - if (inType.isDefinitelyNull()) { - result = InstanceOfResult.FALSE; - } else if (inType.lessThanOrEqual(instanceOfType, appView) && !inType.isNullable()) { - result = InstanceOfResult.TRUE; - } else if (!aliasValue.isPhi() - && aliasValue.definition.isCreatingInstanceOrArray() - && instanceOfType.strictlyLessThan(inType, appView)) { - result = InstanceOfResult.FALSE; - } else if (appView.appInfo().hasLiveness()) { - if (instanceOf.type().isClassType() - && isNeverInstantiatedDirectlyOrIndirectly(instanceOf.type())) { - // The type of the instance-of instruction is a program class, and is never instantiated - // directly or indirectly. Thus, the in-value must be null, meaning that the instance-of - // instruction will always evaluate to false. - result = InstanceOfResult.FALSE; - } - - if (result == InstanceOfResult.UNKNOWN) { - if (inType.isClassType() - && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) { - // The type of the in-value is a program class, and is never instantiated directly or - // indirectly. This, the in-value must be null, meaning that the instance-of instruction - // will always evaluate to false. - result = InstanceOfResult.FALSE; - } - } - - if (result == InstanceOfResult.UNKNOWN) { - Value aliasedValue = - inValue.getSpecificAliasedValue( - value -> - value.isDefinedByInstructionSatisfying( - Instruction::isAssumeWithDynamicTypeAssumption)); - if (aliasedValue != null) { - DynamicTypeWithUpperBound dynamicType = - aliasedValue.getDefinition().asAssume().getDynamicTypeAssumption().getDynamicType(); - Nullability nullability = dynamicType.getNullability(); - if (nullability.isDefinitelyNull()) { - result = InstanceOfResult.FALSE; - } else if (dynamicType.getDynamicUpperBoundType().lessThanOrEqual(instanceOfType, appView) - && (!inType.isNullable() || !nullability.isNullable())) { - result = InstanceOfResult.TRUE; - } - } - } - } - if (result != InstanceOfResult.UNKNOWN) { - ConstNumber newInstruction = - new ConstNumber( - new Value( - code.valueNumberGenerator.next(), - TypeElement.getInt(), - instanceOf.outValue().getLocalInfo()), - result == InstanceOfResult.TRUE ? 1 : 0); - it.replaceCurrentInstruction(newInstruction); - return true; - } - return false; - } - - private boolean isNeverInstantiatedDirectlyOrIndirectly(DexType type) { - assert appView.appInfo().hasLiveness(); - assert type.isClassType(); - DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); - return clazz != null - && !appView.appInfo().withLiveness().isInstantiatedDirectlyOrIndirectly(clazz); - } - public static void removeOrReplaceByDebugLocalWrite( Instruction currentInstruction, InstructionListIterator it, Value inValue, Value outValue) { if (outValue.hasLocalInfo() && outValue.getLocalInfo() != inValue.getLocalInfo()) { @@ -1972,7 +1481,6 @@ public void optimizeAlwaysThrowingInstructions(IRCode code) { assert code.isConsistentSSA(appView); } - private void insertNotNullCheck( BasicBlock block, InstructionListIterator iterator, diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index 435b01ca13..889a43448c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -20,8 +20,8 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; +import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; import com.android.tools.r8.ir.optimize.AssumeRemover; -import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.InliningOracle; import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException; @@ -127,7 +127,6 @@ enum EligibilityStatus { // public final void processMethodCode( AppView appView, - CodeRewriter codeRewriter, StringOptimizer stringOptimizer, EnumValueOptimizer enumValueOptimizer, ProgramMethod method, @@ -248,8 +247,8 @@ public final void processMethodCode( // If a method was inlined we may be able to remove check-cast instructions because we may // have more information about the types of the arguments at the call site. This is // particularly important for bridge methods. - codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions( - code, method, methodProcessor, methodProcessingContext); + new TrivialCheckCastAndInstanceOfRemover(appView) + .run(code, method, methodProcessor, methodProcessingContext); // If a method was inlined we may be able to prune additional branches. new BranchSimplifier(appView).simplifyBranches(code); // If a method was inlined we may see more trivial computation/conversion of String. From 5a6f46b19b840dc652789c424e530401e1fe9191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gjesse?= Date: Thu, 8 Jun 2023 12:44:12 +0200 Subject: [PATCH 043/153] Fix assertion in consistentBlockNumbering Change-Id: I00765a06076451881bd075b0ad718b896ac1fbcf --- src/main/java/com/android/tools/r8/ir/code/IRCode.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index d72358a2e3..9b3ea7c11f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java @@ -833,7 +833,8 @@ public boolean consistentBlockNumbering() { .forEach( (key, value) -> { assert value == 1; - assert value <= basicBlockNumberGenerator.peek(); + assert key >= 0; + assert key <= basicBlockNumberGenerator.peek(); }); return true; } From c04eb1d97999bd1208d389ddab8534928305a141 Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Thu, 8 Jun 2023 13:15:10 +0200 Subject: [PATCH 044/153] Update disabled test after move of interface method desugaring Bug: b/197494749 Change-Id: I1a3f75239cd044651253cd2cd6c5d12de6aea0a6 --- ...faceMethodToNonImmediateInterfaceTest.java | 187 ++++++++++-------- 1 file changed, 101 insertions(+), 86 deletions(-) diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java index f4fec424ce..d743010fb2 100644 --- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java +++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java @@ -7,117 +7,132 @@ import static org.junit.Assert.assertEquals; import com.android.tools.r8.TestBase; -import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.ToolHelper.DexVm.Version; -import com.android.tools.r8.origin.Origin; -import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.AndroidApiLevel; -import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableList; -import org.junit.Ignore; +import java.io.IOException; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.objectweb.asm.Opcodes; @RunWith(Parameterized.class) public class InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest extends TestBase { + private final TestParameters parameters; private final boolean includeInterfaceMethodOnJ; - @Parameterized.Parameters(name = "Include interface method on J: {0}") - public static Boolean[] data() { - return BooleanUtils.values(); + // Note that the expected output is independent of the presence of J.m(). + private static final String EXPECTED = StringUtils.lines("I.m()", "JImpl.m()"); + + @Parameterized.Parameters(name = "{0}, J.m(): {1}") + public static List data() { + return buildParameters( + TestParameters.builder() + .withCfRuntimes() + .enableApiLevelsForCf() + .withDexRuntimes() + .withApiLevel(AndroidApiLevel.B) + .build(), + BooleanUtils.values()); } public InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest( - boolean includeInterfaceMethodOnJ) { + TestParameters parameters, boolean includeInterfaceMethodOnJ) { + this.parameters = parameters; this.includeInterfaceMethodOnJ = includeInterfaceMethodOnJ; } @Test - @Ignore("b/197494749 and b/120130831") - // TODO(b/197494749): Update this test now that desugaring is moved up. In particular this must - // be rewritten with CF based transformers as R8 does not support interface desugaring on DEX. - // TODO(b/120130831): With the move of desugaring this issue should be resolved. The bug indicates - // that a workaround for issues related to the IR implementation can now be reverted. - public void test() throws Exception { - // Note that the expected output is independent of the presence of J.m(). - String expectedOutput = StringUtils.lines("I.m()", "JImpl.m()"); - - byte[] dex = buildProgramDexFileData(); - if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V6_0_1)) { - AndroidApp app = - AndroidApp.builder() - .addDexProgramData(buildProgramDexFileData(), Origin.unknown()) - .build(); - assertEquals(expectedOutput, runOnArt(app, "TestClass")); + public void testJvm() throws Exception { + // The rewritten input is invalid on JVM. + parameters.assumeJvmTestParameters(); + testForJvm(parameters) + .addProgramClasses(getClasses()) + .addProgramClassFileData(getTransformedClasses()) + .run(parameters.getRuntime(), TestClass.class) + .assertFailureWithErrorThatThrows(VerifyError.class); + } + + @Test + public void testD8() throws Exception { + // Notice that desugaring will map out of the invalid invoke. + testForD8(parameters.getBackend()) + .addProgramClasses(getClasses()) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testR8() throws Exception { + // Notice that desugaring will map out of the invalid invoke. + testForR8(parameters.getBackend()) + .addProgramClasses(getClasses()) + .addProgramClassFileData(getTransformedClasses()) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + private List> getClasses() { + return ImmutableList.of(TestClass.class, I.class); + } + + private List getTransformedClasses() throws IOException { + return ImmutableList.of( + transformer(JImpl.class) + .transformMethodInsnInMethod( + "m", + (opcode, owner, name, descriptor, isInterface, visitor) -> { + if (opcode == Opcodes.INVOKESPECIAL) { + assertEquals(owner, binaryName(J.class)); + owner = binaryName(I.class); + } + visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + }) + .transform(), + transformer(J.class) + .removeMethods( + (access, name, descriptor, signature, exceptions) -> + !includeInterfaceMethodOnJ && name.equals("m")) + .transform()); + } + + static class TestClass { + + public static void main(String[] args) { + new JImpl().m(); } + } + + interface I { - testForR8(Backend.DEX) - .addProgramDexFileData(dex) - .addKeepMainRule("TestClass") - .setMinApi(AndroidApiLevel.M) - .run("TestClass") - .assertSuccessWithOutput(expectedOutput); + default void m() { + System.out.println("I.m()"); + } } - // Using Smali instead of Jasmin because interfaces are broken in Jasmin. - private byte[] buildProgramDexFileData() throws Exception { - SmaliBuilder smaliBuilder = new SmaliBuilder(); - smaliBuilder.setMinApi(AndroidApiLevel.N); - - smaliBuilder.addClass("TestClass"); - - // public void main(String[] args) { new JImpl().m(); } - smaliBuilder.addMainMethod( - 1, - "new-instance v0, LJImpl;", - "invoke-direct {v0}, LJImpl;->()V", - "invoke-virtual {v0}, LJImpl;->m()V", - "return-void"); - - smaliBuilder.addInterface("I"); - - // default void m() { System.out.println("In I.m()"); } - smaliBuilder.addInstanceMethod( - "void", - "m", - 2, - "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", - "const-string v1, \"I.m()\"", - "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V", - "return-void"); - - smaliBuilder.addInterface("J", "java.lang.Object", ImmutableList.of("I")); - if (includeInterfaceMethodOnJ) { - smaliBuilder.addInstanceMethod( - "void", - "m", - 2, - "invoke-super {p0}, LI;->m()V", - "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", - "const-string v1, \"J.m()\"", - "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V", - "return-void"); + interface J extends I { + + @Override + default void m() { + I.super.m(); + System.out.println("J.m()"); } + } - smaliBuilder.addClass("JImpl", "java.lang.Object", ImmutableList.of("J")); - smaliBuilder.addDefaultConstructor(); - - // default void m() { I.super.m(); System.out.println("In JImpl.m()"); } - smaliBuilder.addInstanceMethod( - "void", - "m", - 2, - // Note: invoke-super instruction to the non-immediate interface I. - "invoke-super {p0}, LI;->m()V", - "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", - "const-string v1, \"JImpl.m()\"", - "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V", - "return-void"); - - return smaliBuilder.compile(); + static class JImpl implements J { + + @Override + public void m() { + J.super.m(); // Will be rewritten to non-immediate interface: I.super.m(); + System.out.println("JImpl.m()"); + } } } From 1802c82aaf8ccd69236ba4e74ca98e9c664449a7 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 8 Jun 2023 12:33:44 +0200 Subject: [PATCH 045/153] Forward ExecutorService and Timing to AppView#rewriteWithLens Change-Id: I1beb6633f3f4335c583fb1dc2c864c72f9cfee74 --- src/main/java/com/android/tools/r8/R8.java | 39 +++++-------------- .../com/android/tools/r8/graph/AppView.java | 36 ++++++++--------- .../tools/r8/graph/lens/GraphLens.java | 4 ++ .../HorizontalClassMerger.java | 2 +- .../ir/conversion/PrimaryR8IRConverter.java | 3 +- .../ir/optimize/enums/EmptyEnumUnboxer.java | 4 +- .../r8/ir/optimize/enums/EnumUnboxer.java | 4 +- .../r8/ir/optimize/enums/EnumUnboxerImpl.java | 6 ++- .../optimize/enums/EnumUnboxingTreeFixer.java | 5 +-- .../tools/r8/optimize/AccessModifier.java | 38 ++++++++---------- .../tools/r8/optimize/PublicizerLens.java | 10 ++++- .../ArgumentPropagatorApplicationFixer.java | 2 +- .../bridgehoisting/BridgeHoisting.java | 9 ++++- .../r8/optimize/proto/ProtoNormalizer.java | 8 ++-- .../tools/r8/repackaging/Repackaging.java | 20 ++++++---- .../tools/r8/shaking/VerticalClassMerger.java | 4 +- .../r8/synthesis/SyntheticFinalization.java | 5 ++- 17 files changed, 99 insertions(+), 100 deletions(-) diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index d44a39b577..a64aa8e806 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -35,7 +35,6 @@ import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis; import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; import com.android.tools.r8.graph.lens.AppliedGraphLens; -import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger; import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.conversion.IRConverter; @@ -76,7 +75,6 @@ import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker; import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions; import com.android.tools.r8.repackaging.Repackaging; -import com.android.tools.r8.repackaging.RepackagingLens; import com.android.tools.r8.shaking.AbstractMethodRemover; import com.android.tools.r8.shaking.AnnotationRemover; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -447,22 +445,12 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO // to clear the cache, so that we will recompute the type lattice elements. appView.dexItemFactory().clearTypeElementsCache(); - if (options.getProguardConfiguration().isAccessModificationAllowed()) { - SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo(); - GraphLens publicizedLens = - AccessModifier.run( - executorService, - timing, - appViewWithLiveness.appInfo().app(), - appViewWithLiveness, - subtypingInfo); - boolean changed = appView.setGraphLens(publicizedLens); - if (changed) { - // We can now remove redundant bridges. Note that we do not need to update the - // invoke-targets here, as the existing invokes will simply dispatch to the now - // visible super-method. MemberRebinding, if run, will then dispatch it correctly. - new RedundantBridgeRemover(appView.withLiveness()).run(null, executorService); - } + AccessModifier.run(appViewWithLiveness, executorService, timing); + if (appView.graphLens().isPublicizerLens()) { + // We can now remove redundant bridges. Note that we do not need to update the + // invoke-targets here, as the existing invokes will simply dispatch to the now + // visible super-method. MemberRebinding, if run, will then dispatch it correctly. + new RedundantBridgeRemover(appView.withLiveness()).run(null, executorService); } // This pass attempts to reduce the number of nests and nest size to allow further passes, and @@ -597,7 +585,7 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO appView.pruneItems(prunedItems, executorService); - new BridgeHoisting(appViewWithLiveness).run(); + new BridgeHoisting(appViewWithLiveness).run(executorService, timing); assert Inliner.verifyAllSingleCallerMethodsHaveBeenPruned(appViewWithLiveness); assert Inliner.verifyAllMultiCallerInlinedMethodsHaveBeenPruned(appView); @@ -710,19 +698,12 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO // Perform repackaging. if (options.isRepackagingEnabled()) { - DirectMappedDexApplication.Builder appBuilder = - appView.appInfo().app().asDirect().builder(); - RepackagingLens lens = - new Repackaging(appView.withLiveness()).run(appBuilder, executorService, timing); - if (lens != null) { - appView.rewriteWithLensAndApplication(lens, appBuilder.build()); - } + new Repackaging(appView.withLiveness()).run(executorService, timing); } - if (appView.appInfo().hasLiveness()) { - assert Repackaging.verifyIdentityRepackaging(appView.withLiveness()); + if (appView.hasLiveness()) { + assert Repackaging.verifyIdentityRepackaging(appView.withLiveness(), executorService); } - // Clear the reference type lattice element cache. This is required since class merging may // need to build IR. appView.dexItemFactory().clearTypeElementsCache(); diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index 4eff3178ae..c728fda9ca 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -906,32 +906,32 @@ private static void pruneAppInfo( .setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems, executorService)); } - public void rewriteWithLens(NonIdentityGraphLens lens) { - if (lens != null) { - rewriteWithLens(lens, appInfo().app().asDirect(), withClassHierarchy(), lens.getPrevious()); - } - } - - public void rewriteWithLensAndApplication( - NonIdentityGraphLens lens, DirectMappedDexApplication application) { - rewriteWithLensAndApplication(lens, application, lens.getPrevious()); + public void rewriteWithLens( + NonIdentityGraphLens lens, ExecutorService executorService, Timing timing) + throws ExecutionException { + rewriteWithLensAndApplication(lens, app().asDirect(), executorService, timing); } public void rewriteWithLensAndApplication( - NonIdentityGraphLens lens, DirectMappedDexApplication application, GraphLens appliedLens) { - assert lens != null; - assert application != null; - rewriteWithLens(lens, application, withClassHierarchy(), appliedLens); + NonIdentityGraphLens lens, + DirectMappedDexApplication application, + ExecutorService executorService, + Timing timing) + throws ExecutionException { + rewriteWithLensAndApplication( + lens, application, executorService, timing, withClassHierarchy(), lens.getPrevious()); } - private static void rewriteWithLens( + private static void rewriteWithLensAndApplication( NonIdentityGraphLens lens, DirectMappedDexApplication application, + ExecutorService executorService, + Timing timing, AppView appView, - GraphLens appliedLens) { - if (lens == null) { - return; - } + GraphLens appliedLens) + throws ExecutionException { + assert lens != null; + assert application != null; boolean changed = appView.setGraphLens(lens); assert changed; diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index d9f9c79c78..1604073957 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java @@ -441,6 +441,10 @@ public NonIdentityGraphLens asNonIdentityLens() { return null; } + public boolean isPublicizerLens() { + return false; + } + public boolean isVerticalClassMergerLens() { return false; } diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java index 1fbc713b08..abc15a851b 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java @@ -171,7 +171,7 @@ private void run( keepInfo.mutate(mutator -> mutator.removeKeepInfoForMergedClasses(prunedItems)); assert appView.hasClassHierarchy(); appView.rewriteWithLensAndApplication( - horizontalClassMergerGraphLens, newApplication.toDirect()); + horizontalClassMergerGraphLens, newApplication.toDirect(), executorService, timing); } else { assert mode.isFinal(); SyntheticItems syntheticItems = appView.appInfo().getSyntheticItems(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java index cda8f4eeda..bc94785e16 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java @@ -143,7 +143,8 @@ private DexApplication internalOptimize( } outliner.rewriteWithLens(); - enumUnboxer.unboxEnums(appView, this, postMethodProcessorBuilder, executorService, feedback); + enumUnboxer.unboxEnums( + appView, this, postMethodProcessorBuilder, executorService, feedback, timing); appView.unboxedEnums().checkEnumsUnboxed(appView); GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens(); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java index 6f948428bc..3e870d3e30 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java @@ -17,6 +17,7 @@ import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Timing; import java.util.Collections; import java.util.Set; import java.util.concurrent.ExecutorService; @@ -75,7 +76,8 @@ public void unboxEnums( IRConverter converter, Builder postMethodProcessorBuilder, ExecutorService executorService, - OptimizationFeedbackDelayed feedback) { + OptimizationFeedbackDelayed feedback, + Timing timing) { appView.setUnboxedEnums(EnumDataMap.empty()); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java index cfb23b0d20..0910ca124d 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java @@ -17,6 +17,7 @@ import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Timing; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -52,7 +53,8 @@ public abstract void unboxEnums( IRConverter converter, Builder postMethodProcessorBuilder, ExecutorService executorService, - OptimizationFeedbackDelayed feedback) + OptimizationFeedbackDelayed feedback, + Timing timing) throws ExecutionException; public abstract void unsetRewriter(); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java index 48382028b0..bc67f0eca5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java @@ -107,6 +107,7 @@ import com.android.tools.r8.shaking.KeepInfoCollection; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap; import com.android.tools.r8.utils.collections.LongLivedClassSetBuilder; @@ -670,7 +671,8 @@ public void unboxEnums( IRConverter converter, Builder postMethodProcessorBuilder, ExecutorService executorService, - OptimizationFeedbackDelayed feedback) + OptimizationFeedbackDelayed feedback, + Timing timing) throws ExecutionException { assert feedback.noUpdatesLeft(); @@ -718,7 +720,7 @@ public void unboxEnums( EnumUnboxingTreeFixer.Result treeFixerResult = new EnumUnboxingTreeFixer( appView, checkNotNullMethods, enumDataMap, enumClassesToUnbox, utilityClasses) - .fixupTypeReferences(converter, executorService); + .fixupTypeReferences(converter, executorService, timing); EnumUnboxingLens enumUnboxingLens = treeFixerResult.getLens(); // Enqueue the (lens rewritten) methods that require reprocessing. diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java index 189f6031fe..940b63975c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java @@ -142,9 +142,8 @@ private Set getUnboxedEnums() { return enumDataMap.computeAllUnboxedEnums(); } - Result fixupTypeReferences(IRConverter converter, ExecutorService executorService) + Result fixupTypeReferences(IRConverter converter, ExecutorService executorService, Timing timing) throws ExecutionException { - // We do this before so that we can still perform lookup of definitions. fixupSuperEnumClassInitializers(converter, executorService); @@ -156,7 +155,7 @@ Result fixupTypeReferences(IRConverter converter, ExecutorService executorServic Set dispatchMethodReferences = Sets.newIdentityHashSet(); dispatchMethods.forEach((method, code) -> dispatchMethodReferences.add(method.getReference())); EnumUnboxingLens lens = lensBuilder.build(appView, dispatchMethodReferences); - appView.rewriteWithLens(lens); + appView.rewriteWithLens(lens, executorService, timing); // Rewrite outliner with lens. converter.outliner.rewriteWithLens(); diff --git a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java index 43d46ea439..410bec21ac 100644 --- a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java +++ b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java @@ -9,7 +9,6 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -21,12 +20,10 @@ import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.SubtypingInfo; -import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool; import com.android.tools.r8.ir.optimize.MethodPoolCollection; import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.MethodSignatureEquivalence; import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.Timing; @@ -38,22 +35,15 @@ public final class AccessModifier { - private final DexApplication application; private final AppView appView; - private final InternalOptions options; private final SubtypingInfo subtypingInfo; private final MethodPoolCollection methodPoolCollection; private final PublicizedLensBuilder lensBuilder = PublicizerLens.createBuilder(); - private AccessModifier( - DexApplication application, - AppView appView, - SubtypingInfo subtypingInfo) { - this.application = application; + private AccessModifier(AppView appView) { this.appView = appView; - this.options = appView.options(); - this.subtypingInfo = subtypingInfo; + this.subtypingInfo = appView.appInfo().computeSubtypingInfo(); this.methodPoolCollection = // We will add private instance methods when we promote them. new MethodPoolCollection( @@ -66,17 +56,18 @@ private AccessModifier( * *

This will destructively update the DexApplication passed in as argument. */ - public static GraphLens run( - ExecutorService executorService, - Timing timing, - DexApplication application, - AppView appView, - SubtypingInfo subtypingInfo) + public static void run( + AppView appView, ExecutorService executorService, Timing timing) throws ExecutionException { - return new AccessModifier(application, appView, subtypingInfo).run(executorService, timing); + if (appView.options().getProguardConfiguration().isAccessModificationAllowed()) { + timing.begin("Access modification"); + new AccessModifier(appView).internalRun(executorService, timing); + timing.end(); + } } - private GraphLens run(ExecutorService executorService, Timing timing) throws ExecutionException { + private void internalRun(ExecutorService executorService, Timing timing) + throws ExecutionException { // Phase 1: Collect methods to check if private instance methods don't have conflicts. methodPoolCollection.buildAll(executorService, timing); @@ -86,7 +77,10 @@ private GraphLens run(ExecutorService executorService, Timing timing) throws Exe processType(appView.dexItemFactory().objectType); timing.end(); - return lensBuilder.build(appView); + PublicizerLens publicizerLens = lensBuilder.build(appView); + if (publicizerLens != null) { + appView.setGraphLens(publicizerLens); + } } private void doPublicize(ProgramDefinition definition) { @@ -94,7 +88,7 @@ private void doPublicize(ProgramDefinition definition) { } private void processType(DexType type) { - DexProgramClass clazz = asProgramClassOrNull(application.definitionFor(type)); + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); if (clazz != null) { processClass(clazz); } diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java index 8dee6d5369..969887e650 100644 --- a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java +++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java @@ -12,6 +12,7 @@ import com.android.tools.r8.graph.lens.MethodLookupResult; import com.android.tools.r8.graph.lens.NestedGraphLens; import com.android.tools.r8.ir.code.InvokeType; +import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.google.common.collect.Sets; import java.util.Set; @@ -33,6 +34,11 @@ protected boolean isLegitimateToHaveEmptyMappings() { return true; } + @Override + public boolean isPublicizerLens() { + return true; + } + @Override protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) { return previous; @@ -74,9 +80,9 @@ static class PublicizedLensBuilder { private PublicizedLensBuilder() {} - public GraphLens build(AppView appView) { + public PublicizerLens build(AppView appView) { if (publicizedMethods.isEmpty()) { - return appView.graphLens(); + return null; } return new PublicizerLens(appView, publicizedMethods); } diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java index 1f5b5ff90b..4ad22f3a0b 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java @@ -68,7 +68,7 @@ public void fixupApplication( timing.time("Fixup optimization info", () -> fixupOptimizationInfos(executorService)); timing.begin("Rewrite AppView"); - appView.rewriteWithLens(graphLens); + appView.rewriteWithLens(graphLens, executorService, timing); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java index e730778c14..23f81225ca 100644 --- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java +++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java @@ -31,6 +31,7 @@ import com.android.tools.r8.ir.optimize.info.bridge.VirtualBridgeInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.MethodSignatureEquivalence; +import com.android.tools.r8.utils.Timing; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.Iterables; @@ -43,6 +44,8 @@ import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; /** * An optimization pass that hoists bridges upwards with the purpose of sharing redundant bridge @@ -83,7 +86,8 @@ public BridgeHoisting(AppView appView) { this.result = new BridgeHoistingResult(appView); } - public void run() { + public void run(ExecutorService executorService, Timing timing) throws ExecutionException { + timing.begin("Bridge hoisting"); SubtypingInfo subtypingInfo = appView.appInfo().computeSubtypingInfo(); BottomUpClassHierarchyTraversal.forProgramClasses(appView, subtypingInfo) .excludeInterfaces() @@ -95,7 +99,7 @@ public void run() { result.recordNonReboundMethodAccesses(bridgeMethodAccessInfoCollectionBuilder); BridgeHoistingLens lens = result.buildLens(); - appView.rewriteWithLens(lens); + appView.rewriteWithLens(lens, executorService, timing); // Update method access info collection. MethodAccessInfoCollection.Modifier methodAccessInfoCollectionModifier = @@ -118,6 +122,7 @@ public void run() { } }); } + timing.end(); } private void processClass(DexProgramClass clazz, SubtypingInfo subtypingInfo) { diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java index 3c032b354b..54ae131916 100644 --- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java +++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java @@ -59,10 +59,7 @@ public ProtoNormalizer(AppView appView) { } public void run(ExecutorService executorService, Timing timing) throws ExecutionException { - timing.time("Proto normalization", () -> run(executorService)); - } - - private void run(ExecutorService executorService) throws ExecutionException { + timing.begin("Proto normalization"); GlobalReservationState globalReservationState = computeGlobalReservationState(executorService); // To ensure we do not add collisions of method signatures when creating the new method @@ -154,8 +151,9 @@ protected TraversalContinuation joiner( }.run(appView.appInfo().classesWithDeterministicOrder()); if (!lensBuilder.isEmpty()) { - appView.rewriteWithLens(lensBuilder.build()); + appView.rewriteWithLens(lensBuilder.build(), executorService, timing); } + timing.end(); } private GlobalReservationState computeGlobalReservationState(ExecutorService executorService) diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java index ff4fc9276b..77be1dc188 100644 --- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java +++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java @@ -70,16 +70,19 @@ public Repackaging(AppView appView) { appView.options().testing.repackagingConfigurationFactory.apply(appView); } - public RepackagingLens run( - DirectMappedDexApplication.Builder appBuilder, ExecutorService executorService, Timing timing) - throws ExecutionException { + public void run(ExecutorService executorService, Timing timing) throws ExecutionException { timing.begin("Repackage classes"); - RepackagingLens lens = run(appBuilder, executorService); + DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder(); + RepackagingLens lens = repackageClasses(appBuilder, executorService); + if (lens != null) { + appView.rewriteWithLensAndApplication(lens, appBuilder.build(), executorService, timing); + } timing.end(); - return lens; } - public static boolean verifyIdentityRepackaging(AppView appView) { + public static boolean verifyIdentityRepackaging( + AppView appView, ExecutorService executorService) + throws ExecutionException { // Running the tree fixer with an identity mapping helps ensure that the fixup of items is // complete as the rewrite replaces all items regardless of repackaging. // The identity mapping should result in no move callbacks being called. @@ -120,11 +123,12 @@ protected boolean isLegitimateToHaveEmptyMappings() { .builder() .replaceProgramClasses(new ArrayList<>(newProgramClasses)) .build(); - appView.rewriteWithLensAndApplication(emptyRepackagingLens, newApplication); + appView.rewriteWithLensAndApplication( + emptyRepackagingLens, newApplication, executorService, Timing.empty()); return true; } - private RepackagingLens run( + private RepackagingLens repackageClasses( DirectMappedDexApplication.Builder appBuilder, ExecutorService executorService) throws ExecutionException { if (proguardConfiguration.getPackageObfuscationMode().isNone()) { diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index ead40635f9..01b19c3cc6 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java @@ -598,7 +598,7 @@ private boolean typeMayReferenceMergedSourceOrTarget(DexType type) { } } - public VerticalClassMergerGraphLens run() { + public VerticalClassMergerGraphLens run() throws ExecutionException { timing.begin("merge"); // Visit the program classes in a top-down order according to the class hierarchy. TopDownClassHierarchyTraversal.forProgramClasses(appView) @@ -640,7 +640,7 @@ public VerticalClassMergerGraphLens run() { profileCollectionAdditions.commit(appView); // Rewrite collections using the lens. - appView.rewriteWithLens(lens); + appView.rewriteWithLens(lens, executorService, timing); // Copy keep info to newly synthesized methods. keepInfo.mutate( diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java index c955fad580..7f08a9336b 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java @@ -194,7 +194,7 @@ public static void finalizeWithClassHierarchy( appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit)); appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo)); if (result.lens != null) { - appView.rewriteWithLens(result.lens); + appView.rewriteWithLens(result.lens, executorService, timing); } appView.pruneItems(result.prunedItems, executorService); } @@ -206,7 +206,8 @@ public static void finalizeWithLiveness( Result result = appView.getSyntheticItems().computeFinalSynthetics(appView, timing); appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo)); if (result.lens != null) { - appView.rewriteWithLensAndApplication(result.lens, result.commit.getApplication().asDirect()); + appView.rewriteWithLensAndApplication( + result.lens, result.commit.getApplication().asDirect(), executorService, timing); } else { assert result.commit.getApplication() == appView.appInfo().app(); } From 7140856695b1b8a708921613882d3a661b5d4d68 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 8 Jun 2023 12:33:59 +0200 Subject: [PATCH 046/153] Enqueue synthesized methods for processing in second optimization pass Fixes: b/286207150 Change-Id: I48e09b56baa600cb35643d11160921876baac0ab --- .../tools/r8/ir/conversion/IRConverter.java | 6 ++--- .../ir/conversion/PrimaryR8IRConverter.java | 26 +++++++++---------- ...ertionErrorTwoArgsConstructorRewriter.java | 6 +++++ .../r8/ir/optimize/ServiceLoaderRewriter.java | 10 ++++--- .../api/InstanceInitializerOutliner.java | 6 +++++ 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 55c6a062fa..aa7b3714d1 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -120,7 +120,7 @@ public class IRConverter { public final CodeRewriter codeRewriter; public final CommonSubexpressionElimination commonSubexpressionElimination; private final SplitBranch splitBranch; - public final AssertionErrorTwoArgsConstructorRewriter assertionErrorTwoArgsConstructorRewriter; + public AssertionErrorTwoArgsConstructorRewriter assertionErrorTwoArgsConstructorRewriter; private final NaturalIntLoopRemover naturalIntLoopRemover = new NaturalIntLoopRemover(); public final MemberValuePropagation memberValuePropagation; private final LensCodeRewriter lensCodeRewriter; @@ -130,11 +130,11 @@ public class IRConverter { protected final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer; private final StringSwitchRemover stringSwitchRemover; private final TypeChecker typeChecker; - protected final ServiceLoaderRewriter serviceLoaderRewriter; + protected ServiceLoaderRewriter serviceLoaderRewriter; private final EnumValueOptimizer enumValueOptimizer; private final BinopRewriter binopRewriter; protected final EnumUnboxer enumUnboxer; - protected final InstanceInitializerOutliner instanceInitializerOutliner; + protected InstanceInitializerOutliner instanceInitializerOutliner; protected final RemoveVerificationErrorForUnknownReturnedValues removeVerificationErrorForUnknownReturnedValues; diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java index bc94785e16..2840c77386 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java @@ -195,20 +195,6 @@ private DexApplication internalOptimize( Builder builder = appView.appInfo().app().builder(); builder.setHighestSortingString(highestSortingString); - if (serviceLoaderRewriter != null) { - processSimpleSynthesizeMethods( - serviceLoaderRewriter.getSynthesizedServiceLoadMethods(), executorService); - } - - if (instanceInitializerOutliner != null) { - processSimpleSynthesizeMethods( - instanceInitializerOutliner.getSynthesizedMethods(), executorService); - } - if (assertionErrorTwoArgsConstructorRewriter != null) { - processSimpleSynthesizeMethods( - assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executorService); - } - // Update optimization info for all synthesized methods at once. feedback.updateVisibleOptimizationInfo(); @@ -275,9 +261,21 @@ public void waveDone(ProgramMethodSet wave, ExecutorService executorService) private void lastWaveDone( PostMethodProcessor.Builder postMethodProcessorBuilder, ExecutorService executorService) throws ExecutionException { + if (assertionErrorTwoArgsConstructorRewriter != null) { + assertionErrorTwoArgsConstructorRewriter.onLastWaveDone(postMethodProcessorBuilder); + assertionErrorTwoArgsConstructorRewriter = null; + } if (inliner != null) { inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing); } + if (instanceInitializerOutliner != null) { + instanceInitializerOutliner.onLastWaveDone(postMethodProcessorBuilder); + instanceInitializerOutliner = null; + } + if (serviceLoaderRewriter != null) { + serviceLoaderRewriter.onLastWaveDone(postMethodProcessorBuilder); + serviceLoaderRewriter = null; + } // Ensure determinism of method-to-reprocess set. appView.testing().checkDeterminism(postMethodProcessorBuilder::dump); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java index e464f9c013..ca2babb2ce 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java @@ -22,6 +22,7 @@ import com.android.tools.r8.ir.code.NewInstance; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.PostMethodProcessor; import com.android.tools.r8.ir.desugar.backports.BackportedMethods; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.utils.InternalOptions; @@ -47,6 +48,7 @@ public void rewrite( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { + assert !methodProcessor.isPostMethodProcessor(); if (options.canUseAssertionErrorTwoArgumentConstructor()) { return; } @@ -137,4 +139,8 @@ private ProgramMethod createSynthetic( .acceptAssertionErrorCreateMethod(method, methodProcessingContext.getMethodContext()); return method; } + + public void onLastWaveDone(PostMethodProcessor.Builder postMethodProcessorBuilder) { + postMethodProcessorBuilder.addAll(synthesizedMethods, appView.graphLens()); + } } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java index 86d1998d22..9a75fc0c59 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java @@ -24,6 +24,7 @@ import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.PostMethodProcessor; import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -86,14 +87,11 @@ private boolean shouldReportWhyAreYouNotInliningServiceLoaderLoad() { || appInfo.isWhyAreYouNotInliningMethod(serviceLoaderMethods.loadWithClassLoader); } - public List getSynthesizedServiceLoadMethods() { - return synthesizedServiceLoadMethods; - } - public void rewrite( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { + assert !methodProcessor.isPostMethodProcessor(); InstructionListIterator instructionIterator = code.instructionListIterator(); // Create a map from service type to loader methods local to this context since two // service loader calls to the same type in different methods and in the same wave can race. @@ -243,6 +241,10 @@ public void rewrite( } } + public void onLastWaveDone(PostMethodProcessor.Builder postMethodProcessorBuilder) { + postMethodProcessorBuilder.addAll(synthesizedServiceLoadMethods, appView.graphLens()); + } + private void report(Origin origin, DexType serviceLoaderType, String message) { if (reporter != null) { reporter.info( diff --git a/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java index 06c0f92918..1a594fc489 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java @@ -29,6 +29,7 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.PostMethodProcessor; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder; import com.android.tools.r8.ir.synthetic.NewInstanceSourceCode; @@ -66,6 +67,7 @@ public void rewriteInstanceInitializers( ProgramMethod context, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { + assert !methodProcessor.isPostMethodProcessor(); // Do not outline from already synthesized methods. if (context.getDefinition().isD8R8Synthesized()) { return; @@ -175,6 +177,10 @@ public void rewriteInstanceInitializers( } } + public void onLastWaveDone(PostMethodProcessor.Builder postMethodProcessorBuilder) { + postMethodProcessorBuilder.addAll(synthesizedMethods, appView.graphLens()); + } + private boolean canSkipClInit( InstructionListIterator iterator, NewInstance newInstance, Value newInstanceOutValue) { InvokeStatic definition = newInstanceOutValue.getDefinition().asInvokeStatic(); From c07a810a72d53f809a9331da1e8a24771e5a1a13 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 8 Jun 2023 12:34:16 +0200 Subject: [PATCH 047/153] Change NestedGraphLens to inherit from DefaultNonIdentityGraphLens Change-Id: I6184fbb25a05aa490dd23088f889e6681097edcb --- .../android/tools/r8/graph/lens/NestedGraphLens.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java index ee3efbeb1e..694425b561 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java @@ -31,7 +31,7 @@ * #mapInvocationType(DexMethod, DexMethod, InvokeType)} if the default name mapping applies, and * only invocation type might need to change. */ -public class NestedGraphLens extends NonIdentityGraphLens { +public class NestedGraphLens extends DefaultNonIdentityGraphLens { protected static final EmptyBidirectionalOneToOneMap EMPTY_FIELD_MAP = new EmptyBidirectionalOneToOneMap<>(); @@ -306,14 +306,6 @@ public static InvokeType mapVirtualInterfaceInvocationTypes( return type; } - @Override - public boolean isContextFreeForMethods(GraphLens codeLens) { - if (codeLens == this) { - return true; - } - return getPrevious().isContextFreeForMethods(codeLens); - } - @Override public boolean verifyIsContextFreeForMethod(DexMethod method, GraphLens codeLens) { assert codeLens == this From 32c064ab59a17a16a0aaebc8f51a21ecc0cacaf6 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 8 Jun 2023 12:34:36 +0200 Subject: [PATCH 048/153] Add timing to ClassToFeatureSplitMap rewriting Change-Id: Id900e45504c0d91591b88136fe84be6e74d91344 --- .../android/tools/r8/features/ClassToFeatureSplitMap.java | 7 ++++++- src/main/java/com/android/tools/r8/graph/AppView.java | 3 ++- .../com/android/tools/r8/shaking/AppInfoWithLiveness.java | 5 +++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java index 3033f19166..f4ed7cdc3f 100644 --- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java +++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java @@ -21,6 +21,7 @@ import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.Timing; import com.google.common.collect.Sets; import java.util.IdentityHashMap; import java.util.Map; @@ -289,7 +290,11 @@ public boolean isInSameFeatureOrBothInSameBase( == getFeatureSplit(b, options, startupProfile, syntheticItems); } - public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) { + public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens, Timing timing) { + return timing.time("Rewrite ClassToFeatureSplitMap", () -> rewrittenWithLens(lens)); + } + + private ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) { Map rewrittenClassToFeatureSplitMap = new IdentityHashMap<>(); classToFeatureSplitMap.forEach( (type, featureSplit) -> { diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index c728fda9ca..cda6d1f92c 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -980,7 +980,8 @@ private static void rewriteWithLensAndApplication( if (appView.hasLiveness()) { appView .withLiveness() - .setAppInfo(appView.appInfoWithLiveness().rewrittenWithLens(application, lens)); + .setAppInfo( + appView.appInfoWithLiveness().rewrittenWithLens(application, lens, timing)); } else { assert appView.hasClassHierarchy(); AppView appViewWithClassHierarchy = diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java index 472680e35f..ed408072a7 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java @@ -65,6 +65,7 @@ import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.PredicateSet; import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.Visibility; import com.android.tools.r8.utils.WorkList; import com.android.tools.r8.utils.collections.ProgramMethodSet; @@ -1113,7 +1114,7 @@ public AppInfoWithLiveness rebuildWithLiveness(CommittedItems committedItems) { } public AppInfoWithLiveness rewrittenWithLens( - DirectMappedDexApplication application, NonIdentityGraphLens lens) { + DirectMappedDexApplication application, NonIdentityGraphLens lens, Timing timing) { assert checkIfObsolete(); // Switchmap classes should never be affected by renaming. @@ -1129,7 +1130,7 @@ public AppInfoWithLiveness rewrittenWithLens( committedItems.getApplication().getDefinitionsSupplier(committedItems); return new AppInfoWithLiveness( committedItems, - getClassToFeatureSplitMap().rewrittenWithLens(lens), + getClassToFeatureSplitMap().rewrittenWithLens(lens, timing), getMainDexInfo().rewrittenWithLens(getSyntheticItems(), lens), getMissingClasses(), deadProtoTypes, From f82dec87f532764b32d9a905a66f9d1385e16d02 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 06:51:35 +0200 Subject: [PATCH 049/153] Add timing to lens rewriting of AppView Change-Id: I1c23c2955d92e5a0597c702ac25a15301bb15554 --- .../android/tools/r8/graph/AppServices.java | 7 +- .../com/android/tools/r8/graph/AppView.java | 109 +++++++++++------- .../graph/FieldAccessInfoCollectionImpl.java | 7 +- .../tools/r8/graph/FieldAccessInfoImpl.java | 6 +- .../r8/graph/MethodAccessInfoCollection.java | 8 +- .../graph/ObjectAllocationInfoCollection.java | 3 +- .../ObjectAllocationInfoCollectionImpl.java | 7 ++ .../r8/graph/lens/FinalInitClassLens.java | 7 +- .../tools/r8/graph/lens/GraphLens.java | 7 +- .../tools/r8/graph/lens/InitClassLens.java | 3 +- .../r8/graph/lens/ThrowingInitClassLens.java | 3 +- .../HorizontalClassMerger.java | 6 +- .../r8/ir/optimize/enums/EnumUnboxerImpl.java | 4 + ...DefaultOpenClosedInterfacesCollection.java | 3 +- ...onEmptyOpenClosedInterfacesCollection.java | 8 +- .../OpenClosedInterfacesCollection.java | 4 +- .../tools/r8/profile/art/ArtProfile.java | 3 +- .../r8/profile/art/ArtProfileCollection.java | 8 +- .../art/EmptyArtProfileCollection.java | 6 +- .../art/NonEmptyArtProfileCollection.java | 17 ++- .../startup/profile/EmptyStartupProfile.java | 3 +- .../profile/NonEmptyStartupProfile.java | 7 +- .../startup/profile/StartupProfile.java | 3 +- .../tools/r8/shaking/AppInfoWithLiveness.java | 15 +-- .../r8/shaking/AssumeInfoCollection.java | 7 ++ .../DependentMinimumKeepInfoCollection.java | 5 +- .../tools/r8/shaking/KeepInfoCollection.java | 103 +++++++++++------ .../android/tools/r8/shaking/MainDexInfo.java | 8 +- .../shaking/ProguardCompatibilityActions.java | 7 +- .../tools/r8/shaking/RootSetUtils.java | 97 +++++++++------- .../CommittedSyntheticsCollection.java | 3 +- .../r8/synthesis/SyntheticFinalization.java | 4 +- .../tools/r8/synthesis/SyntheticItems.java | 20 ++-- 33 files changed, 336 insertions(+), 172 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java index dc557e94b6..3ef7937687 100644 --- a/src/main/java/com/android/tools/r8/graph/AppServices.java +++ b/src/main/java/com/android/tools/r8/graph/AppServices.java @@ -20,6 +20,7 @@ import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.Timing; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; @@ -129,7 +130,11 @@ public boolean hasServiceImplementationsInFeature( return false; } - public AppServices rewrittenWithLens(GraphLens graphLens) { + public AppServices rewrittenWithLens(GraphLens graphLens, Timing timing) { + return timing.time("Rewrite AppServices", () -> rewrittenWithLens(graphLens)); + } + + private AppServices rewrittenWithLens(GraphLens graphLens) { ImmutableMap.Builder>> rewrittenFeatureMappings = ImmutableMap.builder(); for (Entry>> entry : services.entrySet()) { diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index cda6d1f92c..cc0041b607 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -933,6 +933,8 @@ private static void rewriteWithLensAndApplication( assert lens != null; assert application != null; + timing.begin("Rewrite AppView"); + boolean changed = appView.setGraphLens(lens); assert changed; assert application.verifyWithLens(appView.appInfo().app().asDirect(), lens); @@ -940,38 +942,14 @@ private static void rewriteWithLensAndApplication( // The application has already been rewritten with the given applied lens. Therefore, we // temporarily replace that lens with a lens that does not have any rewritings to avoid the // overhead of traversing the entire lens chain upon each lookup during the rewriting. - NonIdentityGraphLens firstUnappliedLens = lens; - while (firstUnappliedLens.getPrevious() != appliedLens) { - GraphLens previousLens = firstUnappliedLens.getPrevious(); - assert previousLens.isNonIdentityLens(); - assert previousLens != appView.codeLens(); - firstUnappliedLens = previousLens.asNonIdentityLens(); - } + NonIdentityGraphLens firstUnappliedLens = computeFirstUnappliedLens(appView, lens, appliedLens); // Insert a member rebinding lens above the first unapplied lens. // TODO(b/182129249): Once the member rebinding phase has been removed, the MemberRebindingLens // should be removed and all uses of FieldRebindingIdentityLens should be replaced by // MemberRebindingIdentityLens. - GraphLens newMemberRebindingLens = GraphLens.getIdentityLens(); - if (!firstUnappliedLens.isMemberRebindingLens() - && !firstUnappliedLens.isMemberRebindingIdentityLens()) { - NonIdentityGraphLens appliedMemberRebindingLens = - firstUnappliedLens.findPreviousUntil( - previous -> - previous.isMemberRebindingLens() || previous.isMemberRebindingIdentityLens(), - previous -> previous == appView.codeLens()); - if (appliedMemberRebindingLens != null) { - newMemberRebindingLens = - appliedMemberRebindingLens.isMemberRebindingLens() - ? appliedMemberRebindingLens - .asMemberRebindingLens() - .toRewrittenFieldRebindingLens(appView, appliedLens, appliedMemberRebindingLens) - : appliedMemberRebindingLens - .asMemberRebindingIdentityLens() - .toRewrittenMemberRebindingIdentityLens( - appView, appliedLens, appliedMemberRebindingLens); - } - } + GraphLens newMemberRebindingLens = + computeNewMemberRebindingLens(appView, appliedLens, firstUnappliedLens, timing); firstUnappliedLens.withAlternativeParentLens( newMemberRebindingLens, @@ -988,46 +966,95 @@ private static void rewriteWithLensAndApplication( appView.withClassHierarchy(); AppInfoWithClassHierarchy appInfo = appViewWithClassHierarchy.appInfo(); MainDexInfo rewrittenMainDexInfo = - appInfo.getMainDexInfo().rewrittenWithLens(appView.getSyntheticItems(), lens); + appInfo + .getMainDexInfo() + .rewrittenWithLens(appView.getSyntheticItems(), lens, timing); appViewWithClassHierarchy.setAppInfo( appInfo.rebuildWithMainDexInfo(rewrittenMainDexInfo)); } - appView.setAppServices(appView.appServices().rewrittenWithLens(lens)); + appView.setAppServices(appView.appServices().rewrittenWithLens(lens, timing)); appView.setArtProfileCollection( - appView.getArtProfileCollection().rewrittenWithLens(appView, lens)); + appView.getArtProfileCollection().rewrittenWithLens(appView, lens, timing)); appView.setAssumeInfoCollection( appView .getAssumeInfoCollection() - .rewrittenWithLens(appView, lens, appliedLensInModifiedLens)); + .rewrittenWithLens(appView, lens, appliedLensInModifiedLens, timing)); if (appView.hasInitClassLens()) { - appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens)); + appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens, timing)); } if (appView.hasProguardCompatibilityActions()) { appView.setProguardCompatibilityActions( - appView.getProguardCompatibilityActions().rewrittenWithLens(lens)); + appView.getProguardCompatibilityActions().rewrittenWithLens(lens, timing)); } if (appView.hasMainDexRootSet()) { - appView.setMainDexRootSet(appView.getMainDexRootSet().rewrittenWithLens(lens)); + appView.setMainDexRootSet(appView.getMainDexRootSet().rewrittenWithLens(lens, timing)); } appView.setOpenClosedInterfacesCollection( - appView.getOpenClosedInterfacesCollection().rewrittenWithLens(lens)); + appView.getOpenClosedInterfacesCollection().rewrittenWithLens(lens, timing)); if (appView.hasRootSet()) { - appView.setRootSet(appView.rootSet().rewrittenWithLens(lens)); + appView.setRootSet(appView.rootSet().rewrittenWithLens(lens, timing)); } - appView.setStartupProfile(appView.getStartupProfile().rewrittenWithLens(lens)); + appView.setStartupProfile(appView.getStartupProfile().rewrittenWithLens(lens, timing)); }); + + timing.end(); // Rewrite AppView + } + + private static NonIdentityGraphLens computeFirstUnappliedLens( + AppView appView, + NonIdentityGraphLens lens, + GraphLens appliedLens) { + NonIdentityGraphLens firstUnappliedLens = lens; + while (firstUnappliedLens.getPrevious() != appliedLens) { + GraphLens previousLens = firstUnappliedLens.getPrevious(); + assert previousLens.isNonIdentityLens(); + assert previousLens != appView.codeLens(); + firstUnappliedLens = previousLens.asNonIdentityLens(); + } + return firstUnappliedLens; + } + + private static GraphLens computeNewMemberRebindingLens( + AppView appView, + GraphLens appliedLens, + NonIdentityGraphLens firstUnappliedLens, + Timing timing) { + timing.begin("Compute new member rebinding lens"); + GraphLens newMemberRebindingLens = GraphLens.getIdentityLens(); + if (!firstUnappliedLens.isMemberRebindingLens() + && !firstUnappliedLens.isMemberRebindingIdentityLens()) { + NonIdentityGraphLens appliedMemberRebindingLens = + firstUnappliedLens.findPreviousUntil( + previous -> + previous.isMemberRebindingLens() || previous.isMemberRebindingIdentityLens(), + previous -> previous == appView.codeLens()); + if (appliedMemberRebindingLens != null) { + newMemberRebindingLens = + appliedMemberRebindingLens.isMemberRebindingLens() + ? appliedMemberRebindingLens + .asMemberRebindingLens() + .toRewrittenFieldRebindingLens(appView, appliedLens, appliedMemberRebindingLens) + : appliedMemberRebindingLens + .asMemberRebindingIdentityLens() + .toRewrittenMemberRebindingIdentityLens( + appView, appliedLens, appliedMemberRebindingLens); + } + } + timing.end(); + return newMemberRebindingLens; } - public void rewriteWithD8Lens(NonIdentityGraphLens lens) { - rewriteWithD8Lens(lens, withoutClassHierarchy()); + public void rewriteWithD8Lens(NonIdentityGraphLens lens, Timing timing) { + rewriteWithD8Lens(lens, timing, withoutClassHierarchy()); } - private static void rewriteWithD8Lens(NonIdentityGraphLens lens, AppView appView) { + private static void rewriteWithD8Lens( + NonIdentityGraphLens lens, Timing timing, AppView appView) { boolean changed = appView.setGraphLens(lens); assert changed; appView.setArtProfileCollection( - appView.getArtProfileCollection().rewrittenWithLens(appView, lens)); + appView.getArtProfileCollection().rewrittenWithLens(appView, lens, timing)); } public void setAlreadyLibraryDesugared(Set alreadyLibraryDesugared) { diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java index 63e45301d8..53e57f6db4 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java @@ -7,6 +7,7 @@ import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.utils.ObjectUtils; import com.android.tools.r8.utils.SetUtils; +import com.android.tools.r8.utils.Timing; import java.util.IdentityHashMap; import java.util.Map; import java.util.function.BiPredicate; @@ -79,11 +80,12 @@ public void restrictToProgram(DexDefinitionSupplier definitions) { } public FieldAccessInfoCollectionImpl rewrittenWithLens( - DexDefinitionSupplier definitions, GraphLens lens) { + DexDefinitionSupplier definitions, GraphLens lens, Timing timing) { + timing.begin("Rewrite FieldAccessInfoCollectionImpl"); FieldAccessInfoCollectionImpl collection = new FieldAccessInfoCollectionImpl(); Consumer rewriteAndMergeFieldInfo = info -> { - FieldAccessInfoImpl rewrittenInfo = info.rewrittenWithLens(definitions, lens); + FieldAccessInfoImpl rewrittenInfo = info.rewrittenWithLens(definitions, lens, timing); DexField newField = rewrittenInfo.getField(); collection.infos.compute( newField, @@ -91,6 +93,7 @@ public FieldAccessInfoCollectionImpl rewrittenWithLens( ObjectUtils.mapNotNullOrDefault(oldInfo, rewrittenInfo, rewrittenInfo::join)); }; infos.values().forEach(rewriteAndMergeFieldInfo); + timing.end(); return collection; } diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java index 47b1c27a4e..1bc6ae6298 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java @@ -7,6 +7,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts; import com.android.tools.r8.graph.lens.GraphLens; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.Sets; import java.util.IdentityHashMap; @@ -356,11 +357,14 @@ public void clearWrites() { writesWithContexts = AbstractAccessContexts.empty(); } - public FieldAccessInfoImpl rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) { + public FieldAccessInfoImpl rewrittenWithLens( + DexDefinitionSupplier definitions, GraphLens lens, Timing timing) { + timing.begin("Rewrite FieldAccessInfoImpl"); FieldAccessInfoImpl rewritten = new FieldAccessInfoImpl(lens.lookupField(field)); rewritten.flags = flags; rewritten.readsWithContexts = readsWithContexts.rewrittenWithLens(definitions, lens); rewritten.writesWithContexts = writesWithContexts.rewrittenWithLens(definitions, lens); + timing.end(); return rewritten; } diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java index b4dbc3d298..f1fb1b990a 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java +++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java @@ -8,6 +8,7 @@ import com.android.tools.r8.graph.lens.MethodLookupResult; import com.android.tools.r8.ir.code.InvokeType; import com.android.tools.r8.utils.ConsumerUtils; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.Sets; import java.util.IdentityHashMap; @@ -90,14 +91,17 @@ public void forEachVirtualInvokeContext(DexMethod method, Consumer builder = identityBuilder(); rewriteInvokesWithLens(builder, directInvokes, definitions, lens, InvokeType.DIRECT); rewriteInvokesWithLens(builder, interfaceInvokes, definitions, lens, InvokeType.INTERFACE); rewriteInvokesWithLens(builder, staticInvokes, definitions, lens, InvokeType.STATIC); rewriteInvokesWithLens(builder, superInvokes, definitions, lens, InvokeType.SUPER); rewriteInvokesWithLens(builder, virtualInvokes, definitions, lens, InvokeType.VIRTUAL); - return builder.build(); + MethodAccessInfoCollection result = builder.build(); + timing.end(); + return result; } private static void rewriteInvokesWithLens( diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java index 0edf8375cd..70d1666aba 100644 --- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java +++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java @@ -6,6 +6,7 @@ import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.ir.desugar.LambdaDescriptor; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TraversalContinuation; import java.util.Set; import java.util.function.BiConsumer; @@ -42,5 +43,5 @@ void forEachClassWithKnownAllocationSites( AppInfo appInfo); ObjectAllocationInfoCollection rewrittenWithLens( - DexDefinitionSupplier definitions, GraphLens lens); + DexDefinitionSupplier definitions, GraphLens lens, Timing timing); } diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java index 097aa5920d..aea1754fb6 100644 --- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java +++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java @@ -14,6 +14,7 @@ import com.android.tools.r8.shaking.KeepReason; import com.android.tools.r8.shaking.MissingClasses; import com.android.tools.r8.utils.LensUtils; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; @@ -143,6 +144,12 @@ public boolean isAllocationSitesKnown(DexProgramClass clazz) { @Override public ObjectAllocationInfoCollectionImpl rewrittenWithLens( + DexDefinitionSupplier definitions, GraphLens lens, Timing timing) { + return timing.time( + "Rewrite ObjectAllocationInfoCollectionImpl", () -> rewrittenWithLens(definitions, lens)); + } + + private ObjectAllocationInfoCollectionImpl rewrittenWithLens( DexDefinitionSupplier definitions, GraphLens lens) { return builder(true, null).rewrittenWithLens(this, definitions, lens).build(definitions); } diff --git a/src/main/java/com/android/tools/r8/graph/lens/FinalInitClassLens.java b/src/main/java/com/android/tools/r8/graph/lens/FinalInitClassLens.java index 54949fa7d4..9ddac29c95 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/FinalInitClassLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/FinalInitClassLens.java @@ -7,6 +7,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.utils.Timing; import java.util.Map; public class FinalInitClassLens extends InitClassLens { @@ -32,7 +33,11 @@ public boolean isFinal() { } @Override - public InitClassLens rewrittenWithLens(GraphLens lens) { + public InitClassLens rewrittenWithLens(GraphLens lens, Timing timing) { + return timing.time("Rewrite FinalInitClassLens", () -> rewrittenWithLens(lens)); + } + + private InitClassLens rewrittenWithLens(GraphLens lens) { InitClassLens.Builder builder = InitClassLens.builder(); mapping.forEach( (type, field) -> { diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index 1604073957..bbd068e9d1 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java @@ -30,6 +30,7 @@ import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.SetUtils; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.ProgramMethodSet; @@ -491,7 +492,10 @@ public boolean assertReferencesNotModified(Iterable } public Map rewriteCallSites( - Map callSites, DexDefinitionSupplier definitions) { + Map callSites, + DexDefinitionSupplier definitions, + Timing timing) { + timing.begin("Rewrite call sites"); Map result = new IdentityHashMap<>(); LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(definitions, this, null); callSites.forEach( @@ -503,6 +507,7 @@ public Map rewriteCallSites( .add(context); } }); + timing.end(); return result; } diff --git a/src/main/java/com/android/tools/r8/graph/lens/InitClassLens.java b/src/main/java/com/android/tools/r8/graph/lens/InitClassLens.java index 8ec8404300..e1d4ca6838 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/InitClassLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/InitClassLens.java @@ -6,6 +6,7 @@ import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.utils.Timing; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -25,7 +26,7 @@ public boolean isFinal() { return false; } - public abstract InitClassLens rewrittenWithLens(GraphLens lens); + public abstract InitClassLens rewrittenWithLens(GraphLens lens, Timing timing); public static class Builder { diff --git a/src/main/java/com/android/tools/r8/graph/lens/ThrowingInitClassLens.java b/src/main/java/com/android/tools/r8/graph/lens/ThrowingInitClassLens.java index 66a60d50a5..113d0b7331 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/ThrowingInitClassLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/ThrowingInitClassLens.java @@ -7,6 +7,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.utils.Timing; public class ThrowingInitClassLens extends InitClassLens { @@ -24,7 +25,7 @@ public DexField getInitClassField(DexType type) { } @Override - public InitClassLens rewrittenWithLens(GraphLens lens) { + public InitClassLens rewrittenWithLens(GraphLens lens, Timing timing) { return this; } } diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java index abc15a851b..746dbd5896 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java @@ -181,12 +181,12 @@ private void run( .setAppInfo( new AppInfo( syntheticItems.commitRewrittenWithLens( - newApplication, horizontalClassMergerGraphLens), + newApplication, horizontalClassMergerGraphLens, timing), appView .appInfo() .getMainDexInfo() - .rewrittenWithLens(syntheticItems, horizontalClassMergerGraphLens))); - appView.rewriteWithD8Lens(horizontalClassMergerGraphLens); + .rewrittenWithLens(syntheticItems, horizontalClassMergerGraphLens, timing))); + appView.rewriteWithD8Lens(horizontalClassMergerGraphLens, timing); } codeProvider.setGraphLens(horizontalClassMergerGraphLens); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java index bc67f0eca5..5b8e1f7eab 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java @@ -674,6 +674,7 @@ public void unboxEnums( OptimizationFeedbackDelayed feedback, Timing timing) throws ExecutionException { + timing.begin("Unbox enums"); assert feedback.noUpdatesLeft(); assert candidatesToRemoveInWave.isEmpty(); @@ -687,6 +688,7 @@ public void unboxEnums( if (enumUnboxingCandidatesInfo.isEmpty()) { assert enumDataMap.isEmpty(); + timing.end(); return; } @@ -754,6 +756,8 @@ public void unboxEnums( // Ensure determinism of method-to-reprocess set. appView.testing().checkDeterminism(postMethodProcessorBuilder::dump); + + timing.end(); } private void updateOptimizationInfos( diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java index 6759bca0e4..9e3390b49d 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java @@ -7,6 +7,7 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.graph.lens.GraphLens; +import com.android.tools.r8.utils.Timing; /** Default oracle for that answers "maybe open" for each interface. */ public class DefaultOpenClosedInterfacesCollection extends OpenClosedInterfacesCollection { @@ -26,7 +27,7 @@ public boolean isDefinitelyClosed(DexClass clazz) { } @Override - public OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens) { + public OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens, Timing timing) { return this; } diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java index 8553b409d2..b143cc14f5 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java @@ -9,6 +9,7 @@ import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.utils.SetUtils; +import com.android.tools.r8.utils.Timing; import java.util.Set; public class NonEmptyOpenClosedInterfacesCollection extends OpenClosedInterfacesCollection { @@ -26,7 +27,12 @@ public boolean isDefinitelyClosed(DexClass clazz) { } @Override - public OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens) { + public OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens, Timing timing) { + return timing.time( + "Rewrite NonEmptyOpenClosedInterfacesCollection", () -> rewrittenWithLens(graphLens)); + } + + private OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens) { Set rewrittenOpenInterfaceTypes = SetUtils.newIdentityHashSet(openInterfaceTypes.size()); for (DexType openInterfaceType : openInterfaceTypes) { diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java index 648c8a3a53..de5075ecd6 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java @@ -13,6 +13,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Timing; import java.util.function.Supplier; /** @@ -93,7 +94,8 @@ public final boolean isDefinitelyInstanceOfStaticType( return false; } - public abstract OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens); + public abstract OpenClosedInterfacesCollection rewrittenWithLens( + GraphLens graphLens, Timing timing); public abstract OpenClosedInterfacesCollection withoutPrunedItems(PrunedItems prunedItems); } diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java index cc12b860af..784adbf1bd 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java @@ -140,7 +140,8 @@ public ArtProfile rewrittenWithLens(AppView appView, EnumUnboxingLens lens) { }); } - public ArtProfile rewrittenWithLens(NamingLens lens, DexItemFactory dexItemFactory) { + public ArtProfile rewrittenWithLens(AppView appView, NamingLens lens) { + DexItemFactory dexItemFactory = appView.dexItemFactory(); assert !lens.isIdentityLens(); return transform( (classRule, classRuleBuilderFactory) -> diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java index 6bb861a84a..2a3f889573 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java @@ -6,13 +6,13 @@ import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Timing; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -70,10 +70,10 @@ public static EmptyArtProfileCollection empty() { public abstract NonEmptyArtProfileCollection asNonEmpty(); - public abstract ArtProfileCollection rewrittenWithLens(AppView appView, GraphLens lens); - public abstract ArtProfileCollection rewrittenWithLens( - NamingLens lens, DexItemFactory dexItemFactory); + AppView appView, GraphLens lens, Timing timing); + + public abstract ArtProfileCollection rewrittenWithLens(AppView appView, NamingLens lens); public abstract void supplyConsumers(AppView appView); diff --git a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java index 2c63d2edbe..fcd6d413de 100644 --- a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java +++ b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java @@ -5,10 +5,10 @@ package com.android.tools.r8.profile.art; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.utils.Timing; public class EmptyArtProfileCollection extends ArtProfileCollection { @@ -36,12 +36,12 @@ public NonEmptyArtProfileCollection asNonEmpty() { } @Override - public ArtProfileCollection rewrittenWithLens(AppView appView, GraphLens lens) { + public ArtProfileCollection rewrittenWithLens(AppView appView, GraphLens lens, Timing timing) { return this; } @Override - public ArtProfileCollection rewrittenWithLens(NamingLens lens, DexItemFactory dexItemFactory) { + public ArtProfileCollection rewrittenWithLens(AppView appView, NamingLens lens) { return this; } diff --git a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java index d91ebaefd0..51ee7e65be 100644 --- a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java +++ b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java @@ -5,12 +5,12 @@ package com.android.tools.r8.profile.art; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; +import com.android.tools.r8.utils.Timing; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -51,15 +51,20 @@ public Iterator iterator() { } @Override - public NonEmptyArtProfileCollection rewrittenWithLens(AppView appView, GraphLens lens) { + public NonEmptyArtProfileCollection rewrittenWithLens( + AppView appView, GraphLens lens, Timing timing) { + return timing.time( + "Rewrite NonEmptyArtProfileCollection", () -> rewrittenWithLens(appView, lens)); + } + + private NonEmptyArtProfileCollection rewrittenWithLens(AppView appView, GraphLens lens) { return map(artProfile -> artProfile.rewrittenWithLens(appView, lens)); } @Override - public NonEmptyArtProfileCollection rewrittenWithLens( - NamingLens lens, DexItemFactory dexItemFactory) { + public NonEmptyArtProfileCollection rewrittenWithLens(AppView appView, NamingLens lens) { assert !lens.isIdentityLens(); - return map(artProfile -> artProfile.rewrittenWithLens(lens, dexItemFactory)); + return map(artProfile -> artProfile.rewrittenWithLens(appView, lens)); } @Override @@ -75,7 +80,7 @@ public void supplyConsumers(AppView appView) { NonEmptyArtProfileCollection collection = appView.getNamingLens().isIdentityLens() ? this - : rewrittenWithLens(appView.getNamingLens(), appView.dexItemFactory()); + : rewrittenWithLens(appView, appView.getNamingLens()); InternalOptions options = appView.options(); Collection inputs = options.getArtProfileOptions().getArtProfilesForRewriting(); diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java index e585fe6bc2..0030057921 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java +++ b/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java @@ -11,6 +11,7 @@ import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.ThrowingConsumer; +import com.android.tools.r8.utils.Timing; public class EmptyStartupProfile extends StartupProfile { @@ -60,7 +61,7 @@ public boolean isStartupClass(DexType type) { } @Override - public EmptyStartupProfile rewrittenWithLens(GraphLens graphLens) { + public EmptyStartupProfile rewrittenWithLens(GraphLens graphLens, Timing timing) { return this; } diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java index 8f047a4abd..34576c39aa 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java +++ b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java @@ -17,6 +17,7 @@ import com.android.tools.r8.utils.MapUtils; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.ThrowingConsumer; +import com.android.tools.r8.utils.Timing; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -82,7 +83,11 @@ public boolean isStartupClass(DexType type) { } @Override - public StartupProfile rewrittenWithLens(GraphLens graphLens) { + public StartupProfile rewrittenWithLens(GraphLens graphLens, Timing timing) { + return timing.time("Rewrite NonEmptyStartupProfile", () -> rewrittenWithLens(graphLens)); + } + + private StartupProfile rewrittenWithLens(GraphLens graphLens) { return transform( (classRule, builder) -> builder.addClassRule( diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java index 7a517de26a..cee563773a 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java +++ b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java @@ -27,6 +27,7 @@ import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.ThrowingConsumer; +import com.android.tools.r8.utils.Timing; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; @@ -135,7 +136,7 @@ public abstract void forEachRule( public abstract boolean isEmpty(); - public abstract StartupProfile rewrittenWithLens(GraphLens graphLens); + public abstract StartupProfile rewrittenWithLens(GraphLens graphLens, Timing timing); public abstract StartupProfile toStartupProfileForWriting(AppView appView); diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java index ed408072a7..a3ef43f54f 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java @@ -1125,13 +1125,14 @@ public AppInfoWithLiveness rewrittenWithLens( .map(FieldResolutionResult::getResolvedField) .collect(Collectors.toList())); - CommittedItems committedItems = getSyntheticItems().commitRewrittenWithLens(application, lens); + CommittedItems committedItems = + getSyntheticItems().commitRewrittenWithLens(application, lens, timing); DexDefinitionSupplier definitionSupplier = committedItems.getApplication().getDefinitionsSupplier(committedItems); return new AppInfoWithLiveness( committedItems, getClassToFeatureSplitMap().rewrittenWithLens(lens, timing), - getMainDexInfo().rewrittenWithLens(getSyntheticItems(), lens), + getMainDexInfo().rewrittenWithLens(getSyntheticItems(), lens, timing), getMissingClasses(), deadProtoTypes, lens.rewriteReferences(liveTypes), @@ -1141,11 +1142,11 @@ public AppInfoWithLiveness rewrittenWithLens( lens.rewriteReferences(bootstrapMethods), lens.rewriteReferences(virtualMethodsTargetedByInvokeDirect), lens.rewriteReferences(liveMethods), - fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens), - methodAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens), - objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens), - lens.rewriteCallSites(callSites, definitionSupplier), - keepInfo.rewrite(definitionSupplier, lens, application.options), + fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens, timing), + methodAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens, timing), + objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens, timing), + lens.rewriteCallSites(callSites, definitionSupplier, timing), + keepInfo.rewrite(definitionSupplier, lens, application.options, timing), // Take any rule in case of collisions. lens.rewriteReferenceKeys(mayHaveSideEffects, (reference, rules) -> ListUtils.first(rules)), lens.rewriteReferences(alwaysInline), diff --git a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java index 75d76717c1..d9aba6c0cd 100644 --- a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java @@ -15,6 +15,7 @@ import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo; import com.android.tools.r8.utils.MapUtils; +import com.android.tools.r8.utils.Timing; import java.util.IdentityHashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -61,6 +62,12 @@ public boolean isSideEffectFree(DexClassAndMember member) { } public AssumeInfoCollection rewrittenWithLens( + AppView appView, GraphLens graphLens, GraphLens appliedLens, Timing timing) { + return timing.time( + "Rewrite AssumeInfoCollection", () -> rewrittenWithLens(appView, graphLens, appliedLens)); + } + + private AssumeInfoCollection rewrittenWithLens( AppView appView, GraphLens graphLens, GraphLens appliedLens) { Map, AssumeInfo> rewrittenCollection = new IdentityHashMap<>(); backing.forEach( diff --git a/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java index a1129346de..92ba7ffd91 100644 --- a/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java @@ -20,6 +20,7 @@ import com.android.tools.r8.shaking.EnqueuerEvent.UnconditionalKeepInfoEvent; import com.android.tools.r8.shaking.KeepInfo.Joiner; import com.android.tools.r8.utils.MapUtils; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TriConsumer; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -148,7 +149,8 @@ public KeepMethodInfo.Joiner remove(EnqueuerEvent preconditionEvent, DexMethod m return minimumKeepInfoForReference; } - public DependentMinimumKeepInfoCollection rewrittenWithLens(GraphLens graphLens) { + public DependentMinimumKeepInfoCollection rewrittenWithLens(GraphLens graphLens, Timing timing) { + timing.begin("Rewrite DependentMinimumKeepInfoCollection"); DependentMinimumKeepInfoCollection rewrittenDependentMinimumKeepInfo = new DependentMinimumKeepInfoCollection(); forEach( @@ -160,6 +162,7 @@ public DependentMinimumKeepInfoCollection rewrittenWithLens(GraphLens graphLens) .merge(minimumKeepInfo.rewrittenWithLens(graphLens)); } }); + timing.end(); return rewrittenDependentMinimumKeepInfo; } } diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java index fda4b976f4..f9c24747d1 100644 --- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java @@ -29,6 +29,7 @@ import com.android.tools.r8.shaking.KeepFieldInfo.Joiner; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.MapUtils; +import com.android.tools.r8.utils.Timing; import com.google.common.collect.Streams; import java.util.IdentityHashMap; import java.util.Map; @@ -243,7 +244,10 @@ public final boolean isMinificationAllowed( public abstract void forEachPinnedField(Consumer consumer, InternalOptions options); public abstract KeepInfoCollection rewrite( - DexDefinitionSupplier definitions, NonIdentityGraphLens lens, InternalOptions options); + DexDefinitionSupplier definitions, + NonIdentityGraphLens lens, + InternalOptions options, + Timing timing); public abstract KeepInfoCollection mutate(Consumer mutator); @@ -312,7 +316,45 @@ public void removeKeepInfoForPrunedItems(PrunedItems prunedItems) { @Override public KeepInfoCollection rewrite( - DexDefinitionSupplier definitions, NonIdentityGraphLens lens, InternalOptions options) { + DexDefinitionSupplier definitions, + NonIdentityGraphLens lens, + InternalOptions options, + Timing timing) { + timing.begin("Rewrite KeepInfoCollection"); + Map newClassInfo = rewriteClassInfo(lens, options, timing); + Map newMethodInfo = rewriteMethodInfo(lens, options, timing); + Map newFieldInfo = rewriteFieldInfo(lens, options, timing); + MutableKeepInfoCollection result = + new MutableKeepInfoCollection( + newClassInfo, + newMethodInfo, + newFieldInfo, + rewriteRuleInstances( + classRuleInstances, + clazz -> { + DexType rewritten = lens.lookupType(clazz); + if (rewritten.isClassType()) { + return rewritten; + } + assert rewritten.isIntType(); + return null; + }, + KeepClassInfo::newEmptyJoiner), + rewriteRuleInstances( + fieldRuleInstances, + lens::getRenamedFieldSignature, + KeepFieldInfo::newEmptyJoiner), + rewriteRuleInstances( + methodRuleInstances, + lens::getRenamedMethodSignature, + KeepMethodInfo::newEmptyJoiner)); + timing.end(); + return result; + } + + private Map rewriteClassInfo( + NonIdentityGraphLens lens, InternalOptions options, Timing timing) { + timing.begin("Rewrite class info"); Map newClassInfo = new IdentityHashMap<>(keepClassInfo.size()); keepClassInfo.forEach( (type, info) -> { @@ -328,6 +370,30 @@ public KeepInfoCollection rewrite( KeepClassInfo previous = newClassInfo.put(newType, info); assert previous == null; }); + timing.end(); + return newClassInfo; + } + + private Map rewriteFieldInfo( + NonIdentityGraphLens lens, InternalOptions options, Timing timing) { + timing.begin("Rewrite field info"); + Map newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size()); + keepFieldInfo.forEach( + (field, info) -> { + DexField newField = lens.getRenamedFieldSignature(field); + assert newField.name == field.name + || !info.isPinned(options) + || info.isMinificationAllowed(options); + KeepFieldInfo previous = newFieldInfo.put(newField, info); + assert previous == null; + }); + timing.end(); + return newFieldInfo; + } + + private Map rewriteMethodInfo( + NonIdentityGraphLens lens, InternalOptions options, Timing timing) { + timing.begin("Rewrite method info"); Map newMethodInfo = new IdentityHashMap<>(keepMethodInfo.size()); keepMethodInfo.forEach( (method, info) -> { @@ -348,37 +414,8 @@ public KeepInfoCollection rewrite( // TODO(b/169927809): Avoid collisions. // assert previous == null; }); - Map newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size()); - keepFieldInfo.forEach( - (field, info) -> { - DexField newField = lens.getRenamedFieldSignature(field); - assert newField.name == field.name - || !info.isPinned(options) - || info.isMinificationAllowed(options); - KeepFieldInfo previous = newFieldInfo.put(newField, info); - assert previous == null; - }); - return new MutableKeepInfoCollection( - newClassInfo, - newMethodInfo, - newFieldInfo, - rewriteRuleInstances( - classRuleInstances, - clazz -> { - DexType rewritten = lens.lookupType(clazz); - if (rewritten.isClassType()) { - return rewritten; - } - assert rewritten.isIntType(); - return null; - }, - KeepClassInfo::newEmptyJoiner), - rewriteRuleInstances( - fieldRuleInstances, lens::getRenamedFieldSignature, KeepFieldInfo::newEmptyJoiner), - rewriteRuleInstances( - methodRuleInstances, - lens::getRenamedMethodSignature, - KeepMethodInfo::newEmptyJoiner)); + timing.end(); + return newMethodInfo; } private static > Map rewriteRuleInstances( diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java index 69bc36a584..a961e10434 100644 --- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java +++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java @@ -19,6 +19,7 @@ import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.ConsumerUtils; +import com.android.tools.r8.utils.Timing; import com.google.common.collect.Sets; import java.util.Collections; import java.util.Set; @@ -273,7 +274,12 @@ private void ifNotRemoved( } } - public MainDexInfo rewrittenWithLens(SyntheticItems syntheticItems, GraphLens lens) { + public MainDexInfo rewrittenWithLens( + SyntheticItems syntheticItems, GraphLens lens, Timing timing) { + return timing.time("Rewrite MainDexInfo", () -> rewrittenWithLens(syntheticItems, lens)); + } + + private MainDexInfo rewrittenWithLens(SyntheticItems syntheticItems, GraphLens lens) { Set modifiedClassList = Sets.newIdentityHashSet(); classList.forEach( type -> rewriteAndApplyIfNotPrimitiveType(lens, type, modifiedClassList::add)); diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java index b1ec100b34..04098a23eb 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java @@ -8,6 +8,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.graph.lens.GraphLens; +import com.android.tools.r8.utils.Timing; import com.google.common.collect.Sets; import java.util.Set; @@ -37,7 +38,11 @@ public ProguardCompatibilityActions withoutPrunedItems(PrunedItems prunedItems) return builder.build(); } - public ProguardCompatibilityActions rewrittenWithLens(GraphLens lens) { + public ProguardCompatibilityActions rewrittenWithLens(GraphLens lens, Timing timing) { + return timing.time("Rewrite ProguardCompatibilityActions", () -> rewrittenWithLens(lens)); + } + + private ProguardCompatibilityActions rewrittenWithLens(GraphLens lens) { Builder builder = builder(); for (DexType compatInstantiatedType : compatInstantiatedTypes) { builder.addCompatInstantiatedType(lens.lookupType(compatInstantiatedType)); diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java index a8fa6fb951..58567a6bc1 100644 --- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java +++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java @@ -1959,34 +1959,40 @@ public void pruneItems(PrunedItems prunedItems) { } } - public RootSet rewrittenWithLens(GraphLens graphLens) { + public RootSet rewrittenWithLens(GraphLens graphLens, Timing timing) { + timing.begin("Rewrite RootSet"); + RootSet rewrittenRootSet; if (graphLens.isIdentityLens()) { - return this; + rewrittenRootSet = this; + } else { + // TODO(b/164019179): If rules can now reference dead items. These should be pruned or + // rewritten + ifRules.forEach(ProguardIfRule::canReferenceDeadTypes); + rewrittenRootSet = + new RootSet( + getDependentMinimumKeepInfo().rewrittenWithLens(graphLens, timing), + reasonAsked, + alwaysInline, + neverInlineDueToSingleCaller, + bypassClinitForInlining, + whyAreYouNotInlining, + reprocess, + neverReprocess, + alwaysClassInline, + neverClassInline, + noUnusedInterfaceRemoval, + noVerticalClassMerging, + noHorizontalClassMerging, + neverPropagateValue, + mayHaveSideEffects, + dependentKeepClassCompatRule, + identifierNameStrings, + ifRules, + delayedRootSetActionItems, + pendingMethodMoveInverse); } - // TODO(b/164019179): If rules can now reference dead items. These should be pruned or - // rewritten - ifRules.forEach(ProguardIfRule::canReferenceDeadTypes); - return new RootSet( - getDependentMinimumKeepInfo().rewrittenWithLens(graphLens), - reasonAsked, - alwaysInline, - neverInlineDueToSingleCaller, - bypassClinitForInlining, - whyAreYouNotInlining, - reprocess, - neverReprocess, - alwaysClassInline, - neverClassInline, - noUnusedInterfaceRemoval, - noVerticalClassMerging, - noHorizontalClassMerging, - neverPropagateValue, - mayHaveSideEffects, - dependentKeepClassCompatRule, - identifierNameStrings, - ifRules, - delayedRootSetActionItems, - pendingMethodMoveInverse); + timing.end(); + return rewrittenRootSet; } void shouldNotBeMinified(ProgramDefinition definition) { @@ -2311,25 +2317,30 @@ void shouldNotBeMinified(ProgramDefinition definition) { } @Override - public MainDexRootSet rewrittenWithLens(GraphLens graphLens) { + public MainDexRootSet rewrittenWithLens(GraphLens graphLens, Timing timing) { + timing.begin("Rewrite MainDexRootSet"); + MainDexRootSet rewrittenMainDexRootSet; if (graphLens.isIdentityLens()) { - return this; + rewrittenMainDexRootSet = this; + } else { + ImmutableList.Builder rewrittenReasonAsked = ImmutableList.builder(); + reasonAsked.forEach( + reference -> + rewriteAndApplyIfNotPrimitiveType(graphLens, reference, rewrittenReasonAsked::add)); + // TODO(b/164019179): If rules can now reference dead items. These should be pruned or + // rewritten + ifRules.forEach(ProguardIfRule::canReferenceDeadTypes); + // All delayed root set actions should have been processed at this point. + assert delayedRootSetActionItems.isEmpty(); + rewrittenMainDexRootSet = + new MainDexRootSet( + getDependentMinimumKeepInfo().rewrittenWithLens(graphLens, timing), + rewrittenReasonAsked.build(), + ifRules, + delayedRootSetActionItems); } - - ImmutableList.Builder rewrittenReasonAsked = ImmutableList.builder(); - reasonAsked.forEach( - reference -> - rewriteAndApplyIfNotPrimitiveType(graphLens, reference, rewrittenReasonAsked::add)); - // TODO(b/164019179): If rules can now reference dead items. These should be pruned or - // rewritten - ifRules.forEach(ProguardIfRule::canReferenceDeadTypes); - // All delayed root set actions should have been processed at this point. - assert delayedRootSetActionItems.isEmpty(); - return new MainDexRootSet( - getDependentMinimumKeepInfo().rewrittenWithLens(graphLens), - rewrittenReasonAsked.build(), - ifRules, - delayedRootSetActionItems); + timing.end(); + return rewrittenMainDexRootSet; } public MainDexRootSet withoutPrunedItems(PrunedItems prunedItems) { diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java index c4152fae7c..162be3db90 100644 --- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java +++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java @@ -13,6 +13,7 @@ import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; import com.android.tools.r8.utils.IterableUtils; import com.android.tools.r8.utils.SetUtils; +import com.android.tools.r8.utils.Timing; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -307,7 +308,7 @@ CommittedSyntheticsCollection pruneItems(PrunedItems prunedItems) { return changed ? builder.build() : this; } - CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens) { + CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens, Timing timing) { ImmutableSet.Builder syntheticInputsBuilder = ImmutableSet.builder(); return new CommittedSyntheticsCollection( naming, diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java index 7f08a9336b..cf3d62777f 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java @@ -179,8 +179,8 @@ public static void finalize( appView .appInfo() .getMainDexInfo() - .rewrittenWithLens(appView.getSyntheticItems(), result.lens))); - appView.rewriteWithD8Lens(result.lens); + .rewrittenWithLens(appView.getSyntheticItems(), result.lens, timing))); + appView.rewriteWithD8Lens(result.lens, timing); } appView.pruneItems(result.prunedItems, executorService); } diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java index 9f7b7f24e3..e7dc070b62 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java @@ -1081,15 +1081,19 @@ public CommittedItems commitPrunedItems(PrunedItems prunedItems) { } public CommittedItems commitRewrittenWithLens( - DexApplication application, NonIdentityGraphLens lens) { + DexApplication application, NonIdentityGraphLens lens, Timing timing) { + timing.begin("Rewrite SyntheticItems"); assert pending.verifyNotRewritten(lens); - return commit( - PrunedItems.empty(application), - pending, - globalContexts, - committed.rewriteWithLens(lens), - state, - globalSyntheticsStrategy); + CommittedItems committedItems = + commit( + PrunedItems.empty(application), + pending, + globalContexts, + committed.rewriteWithLens(lens, timing), + state, + globalSyntheticsStrategy); + timing.end(); + return committedItems; } private static CommittedItems commit( From 72329d72cef320431fb1a727db718e1c072bd0c9 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 06:51:45 +0200 Subject: [PATCH 050/153] Reuse existing instances in lens rewriting of EnqueuerEvent Change-Id: I9a69655d47e63e661d52167d26b63c7c324dcede --- .../java/com/android/tools/r8/shaking/EnqueuerEvent.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java index 8aea7e43cf..e43b081280 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java @@ -126,6 +126,9 @@ public LiveClassEnqueuerEvent asLiveClassEvent() { @Override public EnqueuerEvent rewrittenWithLens(GraphLens lens) { DexType rewrittenType = lens.lookupType(getType()); + if (rewrittenType == getType()) { + return this; + } if (rewrittenType.isIntType()) { return NoSuchEnqueuerEvent.get(); } @@ -173,6 +176,9 @@ public InstantiatedClassEnqueuerEvent asInstantiatedClassEvent() { @Override public EnqueuerEvent rewrittenWithLens(GraphLens lens) { DexType rewrittenType = lens.lookupType(getType()); + if (rewrittenType == getType()) { + return this; + } if (rewrittenType.isIntType()) { return NoSuchEnqueuerEvent.get(); } From c0a4d1da1a08f859b8cb11f7827fc156a0eeeafb Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Thu, 8 Jun 2023 13:15:04 +0200 Subject: [PATCH 051/153] Migrate some legacy tests using dex-vm property Bug: b/167145686 Change-Id: Ic581de4ef5fb45e78c709f5e272b2e233e9dee7b --- .../vertical/VerticalClassMergerTest.java | 8 +-- .../tools/r8/debug/BlockReorderingTest.java | 64 ++++++++++--------- .../KotlinIntrinsicsIdentifierTest.java | 2 +- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java index b6c66b6835..9826428ebc 100644 --- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java @@ -800,10 +800,10 @@ public void testSuperCallNotRewrittenToDirect() throws Throwable { "In referencedMethod on SuperClassWithReferencedMethod", "SuperClassWithReferencedMethod.referencedMethod()"); - testForD8() + testForD8(parameters.getBackend()) .addProgramFiles(programFiles) .addProgramDexFileData(smaliBuilder.compile()) - .run(main) + .run(parameters.getRuntime(), main) .assertSuccessWithOutput(expectedOutput); testForR8(parameters.getBackend()) @@ -813,7 +813,7 @@ public void testSuperCallNotRewrittenToDirect() throws Throwable { .addKeepRules("-keep class *") .addProgramFiles(programFiles) .addProgramDexFileData(smaliBuilder.compile()) - .run(main) + .run(parameters.getRuntime(), main) .assertSuccessWithOutput(expectedOutput); } @@ -1217,7 +1217,7 @@ private R8TestCompileResult runTestOnInput( String d8Result = testForD8() .addProgramResourceProviders(input.getProgramResourceProviders()) - .run(main) + .run(parameters.getRuntime(), main) .assertSuccess() .getStdOut(); diff --git a/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java b/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java index cee9b2af14..aab70beaa0 100644 --- a/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java +++ b/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java @@ -3,43 +3,53 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.debug; -import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper.DexVm.Version; -import java.util.Collections; +import com.android.tools.r8.utils.AndroidApiLevel; import org.junit.Assume; -import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; -/** - * Test single stepping behaviour across reordered blocks. - */ +/** Test single stepping behaviour across reordered blocks. */ +@RunWith(Parameterized.class) public class BlockReorderingTest extends DebugTestBase { public static final String CLASS = "BlockReordering"; public static final String FILE = "BlockReordering.java"; - private static D8DebugTestConfig d8Config; + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return TestParameters.builder().withDexRuntimes().withApiLevel(AndroidApiLevel.B).build(); + } + + @Parameter public TestParameters parameters; + + public DebugTestConfig getDebugConfig() throws CompilationFailedException { + return testForD8() + .addProgramFiles(DEBUGGEE_JAR) + .setMinApi(parameters) + .addOptionsModification(options -> options.testing.invertConditionals = true) + .compile() + .debugConfig(parameters.getRuntime()); + } - @BeforeClass - public static void setup() { - // Force inversion of all conditionals to reliably construct a regression test for incorrect - // line information when reordering blocks. - d8Config = - new D8DebugTestConfig() - .compileAndAdd( - getStaticTemp(), - Collections.singletonList(DEBUGGEE_JAR), - options -> options.testing.invertConditionals = true); + private void assumeValidTest() { + Assume.assumeFalse( + "Older runtimes incorrectly step out of function: b/67671565", + parameters.isDexRuntimeVersionOlderThanOrEqual(Version.V6_0_1)); } @Test public void testConditionalReturn() throws Throwable { - Assume.assumeTrue( - "Older runtimes incorrectly step out of function: b/67671565", - ToolHelper.getDexVm().getVersion().isNewerThan(Version.V6_0_1)); + assumeValidTest(); final String method = "conditionalReturn"; runDebugTest( - d8Config, + getDebugConfig(), CLASS, breakpoint(CLASS, method), run(), @@ -57,12 +67,10 @@ public void testConditionalReturn() throws Throwable { @Test public void testInvertConditionalReturn() throws Throwable { - Assume.assumeTrue( - "Older runtimes incorrectly step out of function: b/67671565", - ToolHelper.getDexVm().getVersion().isNewerThan(Version.V6_0_1)); + assumeValidTest(); final String method = "invertConditionalReturn"; runDebugTest( - d8Config, + getDebugConfig(), CLASS, breakpoint(CLASS, method), run(), @@ -80,12 +88,10 @@ public void testInvertConditionalReturn() throws Throwable { @Test public void testFallthroughReturn() throws Throwable { - Assume.assumeTrue( - "Older runtimes incorrectly step out of function: b/67671565", - ToolHelper.getDexVm().getVersion().isNewerThan(Version.V6_0_1)); + assumeValidTest(); final String method = "fallthroughReturn"; runDebugTest( - d8Config, + getDebugConfig(), CLASS, breakpoint(CLASS, method), run(), diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java index 63cacfc06a..1191b8a295 100644 --- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java +++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java @@ -175,7 +175,7 @@ private void test( .compile() .assertAllWarningMessagesMatch( equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")) - .run(mainClassName); + .run(testParameters.getRuntime(), mainClassName); CodeInspector codeInspector = result.inspector(); MethodSubject main = codeInspector.clazz(testMain.getClassName()).mainMethod(); From 689dac25766991fb4841167e0dce375e1470954a Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Thu, 8 Jun 2023 12:39:40 +0200 Subject: [PATCH 052/153] [Retrace] Add comments to the retrace stack results Bug: b/284016729 Change-Id: I21c3019065c9b70f8a63c5a43f0cc2d62b33b937 --- .../r8/retrace/RetraceResultWithContext.java | 7 ++++ .../RetraceStackFrameAmbiguousResult.java | 40 +++++++++++++++++++ .../r8/retrace/RetraceStackFrameResult.java | 20 ++++++++++ .../r8/retrace/RetraceStackTraceResult.java | 22 ++++++++++ 4 files changed, 89 insertions(+) diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceResultWithContext.java b/src/main/java/com/android/tools/r8/retrace/RetraceResultWithContext.java index f9dfcdb006..3961591e91 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetraceResultWithContext.java +++ b/src/main/java/com/android/tools/r8/retrace/RetraceResultWithContext.java @@ -9,5 +9,12 @@ @Keep public interface RetraceResultWithContext { + /** + * The current context after retracing stack trace lines. + * + *

Use this context as the next context when retracing additional frames. + * + * @return The stack trace context. + */ RetraceStackTraceContext getContext(); } diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResult.java index 50e59c1f74..b788289f30 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResult.java +++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResult.java @@ -9,15 +9,55 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; +/** + * RetraceStackFrameAmbiguousResult is a potentially empty collection of RetraceStackFrameResult. + * + *

A result is ambiguous if the mapping file contains information that is ambiguous. It can be + * empty if the frame is a compiler synthesized frame. See + * https://r8.googlesource.com/r8/+/main/doc/retrace.md for what information Retrace uses to discard + * frames. + */ @Keep public interface RetraceStackFrameAmbiguousResult { + /** + * Predicate on this result being ambiguous. + * + *

The {@link RetraceStackFrameAmbiguousResult} is ambiguous if size of {@link + * #getAmbiguousResult()} is greater than 1. + * + * @return true if the result is ambiguous. + */ boolean isAmbiguous(); + /** + * Get a list of potential ambiguous results. + * + *

If there is only a single RetraceStackFrameResult it is non-ambiguous. Note that it can also + * be empty, which implies that the result expands unambiguously to an empty stack section. + * + * @return The list of potential ambiguous results. + */ List> getAmbiguousResult(); + /** + * Consume potential ambiguous results. + * + *

If there is only a single RetraceStackFrameResult it is non-ambiguous. Note that it can also + * be empty. + * + * @param consumer The consumer to receive results. + */ void forEach(Consumer> consumer); + /** + * Consume potential ambiguous results. + * + *

If there is only a single RetraceStackFrameResult it non-ambiguous. Note that it can also be + * empty. + * + * @param consumer The consumer to receive the index and result. + */ void forEachWithIndex(BiConsumer, Integer> consumer); int size(); diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResult.java index dcdbf0f7bf..ac0b6fbe04 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResult.java +++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResult.java @@ -8,11 +8,31 @@ import java.util.List; import java.util.function.Consumer; +/** + * {@link RetraceStackFrameResult} is collection of retraced frames from an input frame + context. + * + *

It is guaranteed to be non-ambiguous. It can have more than one frame if it is an inline or an + * outline expansion. It can be empty, fx. if the frames are compiler synthesized. + */ @Keep public interface RetraceStackFrameResult { + /** + * Get a list of retraced frames. + * + *

The first reported result is the innermost frame. + * + * @return the list of retraced frames. + */ List getResult(); + /** + * Consume retraced frames. + * + *

The first reported result is the innermost frame. + * + * @param consumer The consumer to receive results. + */ void forEach(Consumer consumer); int size(); diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceResult.java index ab1e9eb6e1..adcb09a456 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceResult.java +++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceResult.java @@ -8,12 +8,34 @@ import java.util.List; import java.util.function.Consumer; +/** + * A collection of multiple retraced frames. + * + *

The number of elements is the same as the number of input frames. + */ @Keep public interface RetraceStackTraceResult extends RetraceResultWithContext { + /** + * Get a list of results where each element is the result of retracing the original frame. + * + * @return the list of results. + */ List> getResult(); + /** + * Iterate over a potential empty list of ambiguous results. + * + * @param consumer The consumer to accept an ambiguous result. + */ void forEach(Consumer> consumer); + /** + * Predicate to check if the result is empty. + * + *

The result is only empty when no original frames were given. + * + * @return true if the result is empty. + */ boolean isEmpty(); } From 26c5753ccdf8cf5f6da01e1da236156a7f26f78e Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Thu, 8 Jun 2023 13:30:13 +0200 Subject: [PATCH 053/153] [Retrace] Correctly handle holes in compositions Change-Id: I2dd2f8f82efdf538810cdd28b6331ec2743d2df8 --- .../r8/naming/ClassNamingForNameMapper.java | 10 +- .../tools/r8/naming/ComposingBuilder.java | 973 +++++++++--------- .../android/tools/r8/naming/MemberNaming.java | 4 + .../com/android/tools/r8/naming/Range.java | 4 + .../ComposePreambleSyntheticTest.java | 6 +- .../mappingcompose/ComposePreambleTest.java | 2 +- .../mappingcompose/ComposeWithHolesTest.java | 58 ++ 7 files changed, 566 insertions(+), 491 deletions(-) create mode 100644 src/test/java/com/android/tools/r8/mappingcompose/ComposeWithHolesTest.java diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java index da66dfdec7..ab3c1509c8 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java @@ -674,7 +674,7 @@ public Range getOriginalRangeOrIdentity() { } @Override - public Signature getOriginalSignature() { + public MethodSignature getOriginalSignature() { return signature; } @@ -787,9 +787,13 @@ public MappedRange partitionOnMinifiedRange(Range minifiedRange) { Range splitOriginalRange = new Range( getOriginalLineNumber(minifiedRange.from), getOriginalLineNumber(minifiedRange.to)); + return copyWithNewRanges(minifiedRange, splitOriginalRange); + } + + public MappedRange copyWithNewRanges(Range minifiedRange, Range originalRange) { MappedRange splitMappedRange = - new MappedRange(minifiedRange, signature, splitOriginalRange, renamedName); - if (minifiedRange.to >= this.minifiedRange.to) { + new MappedRange(minifiedRange, signature, originalRange, renamedName); + if (minifiedRange.from <= this.minifiedRange.from) { splitMappedRange.additionalMappingInformation = new ArrayList<>(this.additionalMappingInformation); } diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index 502f1203d2..716c7cdd75 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -24,15 +24,14 @@ import com.android.tools.r8.references.MethodReference; import com.android.tools.r8.references.Reference; import com.android.tools.r8.references.TypeReference; -import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.ChainableStringConsumer; import com.android.tools.r8.utils.ConsumerUtils; import com.android.tools.r8.utils.IntBox; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; -import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.SegmentTree; import com.android.tools.r8.utils.ThrowingBiFunction; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntSortedMap; @@ -537,6 +536,50 @@ private MemberNaming getExistingMemberNaming(FieldSignature originalSignature) { return composingClassBuilder.fieldMembers.get(signature); } + private List getExistingMappedRanges(MappedRange newMappedRange) + throws MappingComposeException { + MethodSignature signature = newMappedRange.getOriginalSignature().toUnqualifiedIfQualified(); + SegmentTree> listSegmentTree = methodsWithPosition.get(signature); + // If there are no previously mapped positions for the range check methods without position. + if (listSegmentTree == null) { + MappedRange existingMappedRange = methodsWithoutPosition.get(signature); + return existingMappedRange == null ? null : Collections.singletonList(existingMappedRange); + } + // Any new transformation can be lossy - for example, D8 only promises to keep line + // positions if the method is not throwing (and is not debug). Additionally, R8/PG + // emits `1:1:void foo() -> a` instead of `1:1:void foo():1:1 -> a`, so R8 must + // capture the preamble position by explicitly inserting 0 as original range. + int firstPositionOfOriginalRange = + newMappedRange.getFirstPositionOfOriginalRange(NO_RANGE_FROM); + Entry> firstPositionEntries = + listSegmentTree.findEntry(firstPositionOfOriginalRange); + if (firstPositionEntries != null) { + return firstPositionEntries.getValue(); + } + // If the first position is 0 we can try at find the existing mapped ranges by last + // position. + if (firstPositionOfOriginalRange == 0 && !newMappedRange.isOriginalRangePreamble()) { + Entry> lastPositionEntries = + listSegmentTree.findEntry(newMappedRange.getLastPositionOfOriginalRange()); + if (lastPositionEntries != null) { + return lastPositionEntries.getValue(); + } + } + // We assume that all new minified ranges for a method are rewritten in the new map + // such that no previous existing positions exists. + // The original can be discarded if it no longer exists or if the method is + // non-throwing. + if (!newMappedRange.isOriginalRangePreamble() + && !options.mappingComposeOptions().allowNonExistingOriginalRanges) { + throw new MappingComposeException( + "Could not find original starting position of '" + + newMappedRange.minifiedRange.from + + "' which should be " + + firstPositionOfOriginalRange); + } + return null; + } + private void composeMethodNamings( ClassNamingForNameMapper mapper, ClassNameMapper classNameMapper) throws MappingComposeException { @@ -547,7 +590,7 @@ private void composeMethodNamings( entry.getValue().partitionOnMethodSignature(); for (MappedRangesOfName rangesOfName : mappedRangesOfNames) { MemberNaming memberNaming = rangesOfName.getMemberNaming(mapper); - List newMappedRanges = rangesOfName.getMappedRanges(); + List newRangesToCompose = rangesOfName.getMappedRanges(); RangeBuilder minified = new RangeBuilder(); // The new minified ranges may have ranges that range over positions that has additional // information, such as inlinees: @@ -572,9 +615,9 @@ private void composeMethodNamings( // ... List composedRanges = new ArrayList<>(); ComputedOutlineInformation computedOutlineInformation = new ComputedOutlineInformation(); - List> composedInlineFrames = new ArrayList<>(); - for (int i = 0; i < newMappedRanges.size(); i++) { - MappedRange mappedRange = newMappedRanges.get(i); + List composedInlineFrames = new ArrayList<>(); + for (int i = 0; i < newRangesToCompose.size(); i++) { + MappedRange mappedRange = newRangesToCompose.get(i); minified.addRange(mappedRange.minifiedRange); // Register mapping information that is dependent on the residual naming to allow // updating later on. @@ -583,7 +626,6 @@ private void composeMethodNamings( MethodSignature originalSignature = mappedRange.getOriginalSignature().asMethodSignature(); - // Remove the existing entry if it exists. List existingMappedRanges = null; ComposingClassBuilder existingClassBuilder = getExistingClassBuilder(originalSignature); if (existingClassBuilder != null) { @@ -592,269 +634,32 @@ private void composeMethodNamings( ? originalSignature.toUnqualifiedSignature() : originalSignature) .asMethodSignature(); + // Remove the existing entry since we are now composing the signature. current.addSignatureToRemove(existingClassBuilder, signature); - SegmentTree> listSegmentTree = - existingClassBuilder.methodsWithPosition.get(signature); - if (listSegmentTree != null) { - // Any new transformation can be lossy - for example, D8 only promises to keep line - // positions if the method is not throwing (and is not debug). Additionally, R8/PG - // emits `1:1:void foo() -> a` instead of `1:1:void foo():1:1 -> a`, so R8 must - // capture the preamble position by explicitly inserting 0 as original range. - int firstPositionOfOriginalRange = - mappedRange.getFirstPositionOfOriginalRange(NO_RANGE_FROM); - Entry> existingEntry = - listSegmentTree.findEntry(firstPositionOfOriginalRange); - if (existingEntry == null - && firstPositionOfOriginalRange == 0 - && !mappedRange.isOriginalRangePreamble()) { - existingEntry = - listSegmentTree.findEntry(mappedRange.getLastPositionOfOriginalRange()); - } - // We assume that all new minified ranges for a method are rewritten in the new map - // such that no previous existing positions exists. - if (existingEntry != null) { - existingMappedRanges = existingEntry.getValue(); - } else { - // The original can be discarded if it no longer exists or if the method is - // non-throwing. - if (!mappedRange.isOriginalRangePreamble() - && !options.mappingComposeOptions().allowNonExistingOriginalRanges) { - throw new MappingComposeException( - "Could not find original starting position of '" - + mappedRange.minifiedRange.from - + "' which should be " - + firstPositionOfOriginalRange); - } - } - assert minified.hasValue() || mappedRange.getOriginalRangeOrIdentity() == null; - } else { - MappedRange existingMappedRange = - existingClassBuilder.methodsWithoutPosition.get(signature); - existingMappedRanges = - existingMappedRange == null - ? null - : Collections.singletonList(existingMappedRange); - } - } - // Mapping the original ranges all the way back may cause the minified map range and the - // original mapped range to have different spans. We therefore maintain a collection of - // inline frames to add when we see the last mapped range. - List> newComposedInlineFrames = new ArrayList<>(); - if (composedInlineFrames.isEmpty()) { - splitOnNewMinifiedRange( - composeMappedRangesForMethod( - existingClassBuilder, - existingMappedRanges, - mappedRange, - computedOutlineInformation), - Collections.emptyList(), - newComposedInlineFrames::add); - } else { - for (List composedInlineFrame : composedInlineFrames) { - MappedRange splitMappedRange = - mappedRange.partitionOnMinifiedRange(composedInlineFrame.get(0).minifiedRange); - splitOnNewMinifiedRange( - composeMappedRangesForMethod( - existingClassBuilder, - existingMappedRanges, - splitMappedRange, - computedOutlineInformation), - composedInlineFrame, - newComposedInlineFrames::add); - } + existingMappedRanges = existingClassBuilder.getExistingMappedRanges(mappedRange); + assert existingMappedRanges != null + || minified.hasValue() + || mappedRange.getOriginalRangeOrIdentity() == null; } - composedInlineFrames = newComposedInlineFrames; - if (!isInlineMappedRange(newMappedRanges, i)) { - for (List composedInlineFrame : composedInlineFrames) { - composedRanges.addAll(composedInlineFrame); - } + // Now that we have obtained a potential existing mapping, we can compose the frames. + List newComposedRange = + composeMappedRangesForMethod( + existingClassBuilder, + existingMappedRanges, + mappedRange, + computedOutlineInformation); + composedInlineFrames = composeInlineFrames(newComposedRange, composedInlineFrames); + if (!isInlineMappedRange(newRangesToCompose, i)) { + composedRanges.addAll(composedInlineFrames); composedInlineFrames = Collections.emptyList(); } } - // Check if we could have inlined an outline which is true if we see both an outline and - // call site to patch up. - if (!computedOutlineInformation.seenOutlineMappingInformation.isEmpty() - && !computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) { - Set outlineCallSitesToRemove = - Sets.newIdentityHashSet(); - Set outlinesToRemove = Sets.newIdentityHashSet(); - // We patch up all ranges from top to bottom with the invariant that all at a given - // index, all above have been updated correctly. We will do expansion of frames - // when separating out single minified lines, but we keep the outline information - // present such that we can fix them when seeing them later. - int composedRangeIndex = 0; - while (composedRangeIndex < composedRanges.size() - 1) { - MappedRange outline = composedRanges.get(composedRangeIndex++); - if (outline.isOutlineFrame() - && outline.minifiedRange.equals( - composedRanges.get(composedRangeIndex).minifiedRange)) { - // We should replace the inlined outline frame positions with the synthesized - // positions from the outline call site. - MappedRange outlineCallSite = composedRanges.get(composedRangeIndex); - if (outlineCallSite.getOutlineCallsiteInformation().size() != 1) { - // If we have an inlined outline it must be such that the outer frame is an - // outline callsite. - throw new MappingComposeException( - "Expected exactly one outline call site for a mapped range with signature '" - + outlineCallSite.getOriginalSignature() - + "'."); - } - OutlineCallsiteMappingInformation outlineCallSiteInformation = - outlineCallSite.getOutlineCallsiteInformation().get(0); - // The original positions in the outline callsite have been composed, so we have to - // find the existing mapped range and iterate the original positions for that range. - ComputedMappedRangeForOutline computedInformationForCallSite = - computedOutlineInformation.getComputedRange( - outlineCallSiteInformation, outlineCallSite); - if (computedInformationForCallSite == null) { - continue; - } - Map> mappedRangesForOutline = - new HashMap<>(outlineCallSiteInformation.getPositions().size()); - visitOutlineMappedPositions( - outlineCallSiteInformation, - computedInformationForCallSite - .current - .getOriginalSignature() - .asMethodSignature(), - mappedRangesForOutline::put); - List newComposedRanges = new ArrayList<>(); - // Copy all previous handled mapped ranges into a new list. - for (MappedRange previousMappedRanges : composedRanges) { - if (previousMappedRanges == outline) { - break; - } - newComposedRanges.add(previousMappedRanges); - } - // The original positions in the outline have been composed, so we have to find the - // existing mapped range and iterate the original positions for that range. - ComputedMappedRangeForOutline computedInformationForOutline = - computedOutlineInformation.getComputedRange( - outline.getOutlineMappingInformation(), outline); - if (computedInformationForOutline == null) { - continue; - } - // The outline could have additional inlined positions in it, but we should be - // guaranteed to find call site information on all original line numbers. We - // therefore iterate one by one and amend the subsequent outer frames as well. - MappedRange current = computedInformationForOutline.current; - int minifiedLine = outline.minifiedRange.from; - for (int originalLine = current.getOriginalRangeOrIdentity().from; - originalLine <= current.getOriginalRangeOrIdentity().to; - originalLine++) { - // If the outline is itself an inline frame it is bound to only have one original - // position and we can simply insert all inline frames on that position with the - // existing minified range. - Range newMinifiedRange = - outline.originalRange.isCardinal - ? outline.minifiedRange - : new Range(minifiedLine, minifiedLine); - List outlineMappedRanges = mappedRangesForOutline.get(originalLine); - if (outlineMappedRanges != null) { - outlineMappedRanges.forEach( - range -> { - if (range != ListUtils.last(outlineMappedRanges)) { - newComposedRanges.add( - new MappedRange( - newMinifiedRange, - range.getOriginalSignature().asMethodSignature(), - range.originalRange, - outlineCallSite.getRenamedName())); - } - }); - newComposedRanges.add( - new MappedRange( - newMinifiedRange, - outlineCallSite.getOriginalSignature().asMethodSignature(), - ListUtils.last(outlineMappedRanges).originalRange, - outlineCallSite.getRenamedName())); - } - for (int tailInlineFrameIndex = composedRangeIndex + 1; - tailInlineFrameIndex < composedRanges.size(); - tailInlineFrameIndex++) { - MappedRange originalMappedRange = composedRanges.get(tailInlineFrameIndex); - if (!originalMappedRange.minifiedRange.equals(outlineCallSite.minifiedRange)) { - break; - } - MappedRange newMappedRange = - originalMappedRange.withMinifiedRange(newMinifiedRange); - newMappedRange.setAdditionalMappingInformationInternal( - originalMappedRange.getAdditionalMappingInformation()); - newComposedRanges.add(newMappedRange); - } - minifiedLine++; - } - // We have patched up the the inlined outline and all subsequent inline frames - // (although some of the subsequent frames above could also be inlined outlines). We - // therefore need to copy the remaining frames. - boolean seenMinifiedRange = false; - for (MappedRange range : composedRanges) { - if (range.minifiedRange.equals(outline.minifiedRange)) { - seenMinifiedRange = true; - } else if (seenMinifiedRange) { - newComposedRanges.add(range); - } - } - composedRanges = newComposedRanges; - outlineCallSitesToRemove.add(outlineCallSiteInformation); - outlinesToRemove.add(outline.getOutlineMappingInformation()); - } - } - // If we removed any outlines or call site, remove the processing of them. - outlineCallSitesToRemove.forEach( - computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp::remove); - outlinesToRemove.forEach( - computedOutlineInformation.seenOutlineMappingInformation::remove); - } - if (!computedOutlineInformation.seenOutlineMappingInformation.isEmpty()) { - MappedRange lastComposedRange = ListUtils.last(composedRanges); - current - .getUpdateOutlineCallsiteInformation( - committedPreviousClassBuilder.getRenamedName(), - ListUtils.last(newMappedRanges).signature.getName(), - lastComposedRange.getRenamedName()) - .setNewMappedRanges(newMappedRanges); - } - if (!computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) { - MappedRange lastComposedRange = ListUtils.last(composedRanges); - List composedRangesFinal = composedRanges; - // Outline positions are synthetic positions and they have no position in the residual - // program. We therefore have to find the original positions and copy all inline frames - // and amend the outermost frame with the residual signature and the next free position. - List outlineCallSites = - new ArrayList<>( - computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.keySet()); - outlineCallSites.sort(Comparator.comparing(mapping -> mapping.getOutline().toString())); - IntBox firstAvailableRange = new IntBox(lastComposedRange.minifiedRange.to + 1); - for (OutlineCallsiteMappingInformation outlineCallSite : outlineCallSites) { - Int2IntSortedMap newPositionMap = - new Int2IntLinkedOpenHashMap(outlineCallSite.getPositions().size()); - visitOutlineMappedPositions( - outlineCallSite, + composedRanges = + fixupOutlineInformation( + computedOutlineInformation, memberNaming.getOriginalSignature().asMethodSignature(), - (originalPosition, mappedRangesForOutlinePosition) -> { - int newIndex = firstAvailableRange.getAndIncrement(); - Range newMinifiedRange = new Range(newIndex, newIndex); - MappedRange outerMostOutlineFrame = - ListUtils.last(mappedRangesForOutlinePosition); - for (MappedRange inlineMappedRangeInOutlinePosition : - mappedRangesForOutlinePosition) { - if (inlineMappedRangeInOutlinePosition != outerMostOutlineFrame) { - composedRangesFinal.add( - inlineMappedRangeInOutlinePosition.withMinifiedRange(newMinifiedRange)); - } - } - composedRangesFinal.add( - new MappedRange( - newMinifiedRange, - lastComposedRange.signature, - outerMostOutlineFrame.originalRange, - lastComposedRange.getRenamedName())); - newPositionMap.put((int) originalPosition, newIndex); - outlineCallSite.setPositionsInternal(newPositionMap); - }); - } - } + newRangesToCompose, + composedRanges); MethodSignature residualSignature = memberNaming .computeResidualSignature(type -> inverseClassMapping.getOrDefault(type, type)) @@ -873,6 +678,217 @@ private void composeMethodNamings( } } + private List fixupOutlineInformation( + ComputedOutlineInformation computedOutlineInformation, + MethodSignature originalSignature, + List newRangesToBeComposed, + List composedRanges) + throws MappingComposeException { + composedRanges = fixupInlinedOutlines(computedOutlineInformation, composedRanges); + fixupOutlineInformation(computedOutlineInformation, newRangesToBeComposed, composedRanges); + fixupOutlineCallsiteInformation( + computedOutlineInformation, originalSignature, composedRanges); + return composedRanges; + } + + private List fixupInlinedOutlines( + ComputedOutlineInformation computedOutlineInformation, List composedRanges) + throws MappingComposeException { + // Check if we could have inlined an outline which is true if we see both an outline and call + // site to patch up. When an outline has been inlined we have to retrace through the inlined + // positions, by positions only existing in the previous mapped ranges, to form new ranges. + if (computedOutlineInformation.seenOutlineMappingInformation.isEmpty() + || computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) { + return composedRanges; + } + Set outlineCallSitesToRemove = Sets.newIdentityHashSet(); + Set outlinesToRemove = Sets.newIdentityHashSet(); + // We patch up all ranges from top to bottom with the invariant that all at a given + // index, all above have been updated correctly. We will do expansion of frames + // when separating out single minified lines, but we keep the outline information + // present such that we can fix them when seeing them later. + int composedRangeIndex = 0; + while (composedRangeIndex < composedRanges.size() - 1) { + MappedRange outline = composedRanges.get(composedRangeIndex++); + if (outline.isOutlineFrame() + && outline.minifiedRange.equals(composedRanges.get(composedRangeIndex).minifiedRange)) { + // We should replace the inlined outline frame positions with the synthesized + // positions from the outline call site. + MappedRange outlineCallSite = composedRanges.get(composedRangeIndex); + if (outlineCallSite.getOutlineCallsiteInformation().size() != 1) { + // If we have an inlined outline it must be such that the outer frame is an + // outline callsite. + throw new MappingComposeException( + "Expected exactly one outline call site for a mapped range with signature '" + + outlineCallSite.getOriginalSignature() + + "'."); + } + OutlineCallsiteMappingInformation outlineCallSiteInformation = + outlineCallSite.getOutlineCallsiteInformation().get(0); + // The original positions in the outline callsite have been composed, so we have to + // find the existing mapped range and iterate the original positions for that range. + ComputedMappedRangeForOutline computedInformationForCallSite = + computedOutlineInformation.getComputedRange( + outlineCallSiteInformation, outlineCallSite); + if (computedInformationForCallSite == null) { + continue; + } + Map> mappedRangesForOutline = + new HashMap<>(outlineCallSiteInformation.getPositions().size()); + visitOutlineMappedPositions( + outlineCallSiteInformation, + computedInformationForCallSite.current.getOriginalSignature(), + mappedRangesForOutline::put); + List newComposedRanges = new ArrayList<>(); + // Copy all previous handled mapped ranges into a new list. + for (MappedRange previousMappedRanges : composedRanges) { + if (previousMappedRanges == outline) { + break; + } + newComposedRanges.add(previousMappedRanges); + } + // The original positions in the outline have been composed, so we have to find the + // existing mapped range and iterate the original positions for that range. + ComputedMappedRangeForOutline computedInformationForOutline = + computedOutlineInformation.getComputedRange( + outline.getOutlineMappingInformation(), outline); + if (computedInformationForOutline == null) { + continue; + } + // The outline could have additional inlined positions in it, but we should be + // guaranteed to find call site information on all original line numbers. We + // therefore iterate one by one and amend the subsequent outer frames as well. + MappedRange current = computedInformationForOutline.current; + int minifiedLine = outline.minifiedRange.from; + for (int originalLine = current.getOriginalRangeOrIdentity().from; + originalLine <= current.getOriginalRangeOrIdentity().to; + originalLine++) { + // If the outline is itself an inline frame it is bound to only have one original + // position and we can simply insert all inline frames on that position with the + // existing minified range. + Range newMinifiedRange = + outline.originalRange.isCardinal + ? outline.minifiedRange + : new Range(minifiedLine, minifiedLine); + List outlineMappedRanges = mappedRangesForOutline.get(originalLine); + if (outlineMappedRanges != null) { + outlineMappedRanges.forEach( + range -> { + if (range != ListUtils.last(outlineMappedRanges)) { + newComposedRanges.add( + new MappedRange( + newMinifiedRange, + range.getOriginalSignature().asMethodSignature(), + range.originalRange, + outlineCallSite.getRenamedName())); + } + }); + newComposedRanges.add( + new MappedRange( + newMinifiedRange, + outlineCallSite.getOriginalSignature().asMethodSignature(), + ListUtils.last(outlineMappedRanges).originalRange, + outlineCallSite.getRenamedName())); + } + for (int tailInlineFrameIndex = composedRangeIndex + 1; + tailInlineFrameIndex < composedRanges.size(); + tailInlineFrameIndex++) { + MappedRange originalMappedRange = composedRanges.get(tailInlineFrameIndex); + if (!originalMappedRange.minifiedRange.equals(outlineCallSite.minifiedRange)) { + break; + } + MappedRange newMappedRange = originalMappedRange.withMinifiedRange(newMinifiedRange); + newMappedRange.setAdditionalMappingInformationInternal( + originalMappedRange.getAdditionalMappingInformation()); + newComposedRanges.add(newMappedRange); + } + minifiedLine++; + } + // We have patched up the the inlined outline and all subsequent inline frames + // (although some of the subsequent frames above could also be inlined outlines). We + // therefore need to copy the remaining frames. + boolean seenMinifiedRange = false; + for (MappedRange range : composedRanges) { + if (range.minifiedRange.equals(outline.minifiedRange)) { + seenMinifiedRange = true; + } else if (seenMinifiedRange) { + newComposedRanges.add(range); + } + } + composedRanges = newComposedRanges; + outlineCallSitesToRemove.add(outlineCallSiteInformation); + outlinesToRemove.add(outline.getOutlineMappingInformation()); + } + } + // If we removed any outlines or call sites, remove the processing of them. + outlineCallSitesToRemove.forEach( + computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp::remove); + outlinesToRemove.forEach(computedOutlineInformation.seenOutlineMappingInformation::remove); + return composedRanges; + } + + private void fixupOutlineInformation( + ComputedOutlineInformation computedOutlineInformation, + List newRangesToBeComposed, + List composedRanges) { + if (computedOutlineInformation.seenOutlineMappingInformation.isEmpty()) { + return; + } + MappedRange lastComposedRange = ListUtils.last(composedRanges); + current + .getUpdateOutlineCallsiteInformation( + committedPreviousClassBuilder.getRenamedName(), + ListUtils.last(newRangesToBeComposed).signature.getName(), + lastComposedRange.getRenamedName()) + .setNewMappedRanges(newRangesToBeComposed); + } + + private void fixupOutlineCallsiteInformation( + ComputedOutlineInformation computedOutlineInformation, + MethodSignature originalSignature, + List composedRanges) + throws MappingComposeException { + if (computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) { + return; + } + MappedRange lastComposedRange = ListUtils.last(composedRanges); + // Outline positions are synthetic positions, and they have no position in the residual + // program. We therefore have to find the original positions and copy all inline frames + // and amend the outermost frame with the residual signature and the next free position. + List outlineCallSites = + new ArrayList<>( + computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.keySet()); + outlineCallSites.sort(Comparator.comparing(mapping -> mapping.getOutline().toString())); + IntBox firstAvailableRange = new IntBox(lastComposedRange.minifiedRange.to + 1); + for (OutlineCallsiteMappingInformation outlineCallSite : outlineCallSites) { + Int2IntSortedMap newPositionMap = + new Int2IntLinkedOpenHashMap(outlineCallSite.getPositions().size()); + visitOutlineMappedPositions( + outlineCallSite, + originalSignature, + (originalPosition, mappedRangesForOutlinePosition) -> { + int newIndex = firstAvailableRange.getAndIncrement(); + Range newMinifiedRange = new Range(newIndex, newIndex); + MappedRange outerMostOutlineFrame = ListUtils.last(mappedRangesForOutlinePosition); + for (MappedRange inlineMappedRangeInOutlinePosition : + mappedRangesForOutlinePosition) { + if (inlineMappedRangeInOutlinePosition != outerMostOutlineFrame) { + composedRanges.add( + inlineMappedRangeInOutlinePosition.withMinifiedRange(newMinifiedRange)); + } + } + composedRanges.add( + new MappedRange( + newMinifiedRange, + lastComposedRange.signature, + outerMostOutlineFrame.originalRange, + lastComposedRange.getRenamedName())); + newPositionMap.put((int) originalPosition, newIndex); + outlineCallSite.setPositionsInternal(newPositionMap); + }); + } + } + private void visitOutlineMappedPositions( OutlineCallsiteMappingInformation outlineCallSite, MethodSignature originalSignature, @@ -903,10 +919,12 @@ private void visitOutlineMappedPositions( + originalSignature + "'."); } - ExistingMapping existingMapping = computeExistingMapping(mappedRanges); + ExistingMappings existingMappings = ExistingMappings.create(mappedRanges); List mappedRangesForOutlinePosition = - existingMapping.getMappedRangesForPosition(originalDestination); - if (mappedRangesForOutlinePosition == null) { + existingMappings.getNextRanges(originalDestination); + if (mappedRangesForOutlinePosition == null + || mappedRangesForOutlinePosition.isEmpty() + || !mappedRangesForOutlinePosition.get(0).minifiedRange.contains(originalDestination)) { throw new MappingComposeException( "Could not find ranges for outline position '" + keyPosition @@ -918,36 +936,56 @@ private void visitOutlineMappedPositions( } } - private void splitOnNewMinifiedRange( - List mappedRanges, - List previouslyMapped, - Consumer> consumer) { - assert !mappedRanges.isEmpty(); - Range minifiedRange = mappedRanges.get(0).minifiedRange; - if (minifiedRange == null) { - consumer.accept(ListUtils.joinNewArrayList(previouslyMapped, mappedRanges)); - return; + /** + * Utility function to compose inline frames + * + *

Say that we have the following input: + * + *

+     *   previousInlineFrames:
+     *     10:10:void m1():10:10 -> w
+     *     10:10:void y():30 -> w
+     *     11:11:void m2(int):20:20 -> w
+     *     11:11:void y():31 -> w
+     *   newComposedRanges:
+     *     10:11:void new_synthetic_method():0 -> w
+     * 
+ * + * The newComposedRanges are outer frames, but we cannot place them below since their range + * overlap. We therefore need to place a copy of newComposedRanges into the correct position at + * the minified range to produce: + * + *
+     *   result:
+     *     10:10:void m1():10 -> w
+     *     10:10:void y():30:30 -> w
+     *     10:10:void new_synthetic_method():0 -> w
+     *     11:11:void m2(int):20 -> w
+     *     11:11:void y():31:31 -> w
+     *     11:11:void new_synthetic_method():0 -> w
+     * 
+ */ + private List composeInlineFrames( + List newComposedRanges, List previousInlineFrames) { + if (previousInlineFrames.isEmpty()) { + return newComposedRanges; } - Box lastMinifiedRange = new Box<>(minifiedRange); - int lastMappedIndex = 0; - for (int i = 0; i < mappedRanges.size(); i++) { - MappedRange mappedRange = mappedRanges.get(i); - Range lastMinifiedRangeFinal = lastMinifiedRange.get(); - if (!mappedRange.minifiedRange.equals(lastMinifiedRangeFinal)) { - consumer.accept( - ListUtils.joinNewArrayList( - ListUtils.map( - previouslyMapped, x -> x.partitionOnMinifiedRange(lastMinifiedRangeFinal)), - mappedRanges.subList(lastMappedIndex, i))); - lastMinifiedRange.set(mappedRange.minifiedRange); - lastMappedIndex = i; + List newComposedInlineFrames = new ArrayList<>(); + Range lastMinifiedRange = previousInlineFrames.get(0).minifiedRange; + for (MappedRange previousFrame : previousInlineFrames) { + if (!lastMinifiedRange.equals(previousFrame.minifiedRange)) { + for (MappedRange newComposedRange : newComposedRanges) { + newComposedInlineFrames.add( + newComposedRange.partitionOnMinifiedRange(lastMinifiedRange)); + } + lastMinifiedRange = previousFrame.minifiedRange; } + newComposedInlineFrames.add(previousFrame.partitionOnMinifiedRange(lastMinifiedRange)); } - consumer.accept( - ListUtils.joinNewArrayList( - ListUtils.map( - previouslyMapped, x -> x.partitionOnMinifiedRange(lastMinifiedRange.get())), - mappedRanges.subList(lastMappedIndex, mappedRanges.size()))); + for (MappedRange newComposedRange : newComposedRanges) { + newComposedInlineFrames.add(newComposedRange.partitionOnMinifiedRange(lastMinifiedRange)); + } + return newComposedInlineFrames; } private ComposingClassBuilder getExistingClassBuilder(MethodSignature originalSignature) { @@ -991,229 +1029,196 @@ private List composeMappedRangesForMethod( ComputedOutlineInformation computedOutlineInformation) throws MappingComposeException { assert newRange != null; + // If there are no existing ranges, then just return the new range. if (existingRanges == null || existingRanges.isEmpty()) { return Collections.singletonList(newRange); } MappedRange lastExistingRange = ListUtils.last(existingRanges); + // Check if there are no range information in the original range. If not we do a simple + // composition of methods. if (newRange.getOriginalRangeOrIdentity() == null) { - MappedRange newComposedRange = - new MappedRange( - newRange.minifiedRange, - potentiallyQualifySignature( - newRange.signature, - lastExistingRange.signature, - existingClassBuilder.getOriginalName()), - null, - newRange.renamedName); - composeMappingInformation( - newComposedRange.getAdditionalMappingInformation(), - lastExistingRange.getAdditionalMappingInformation(), - info -> newComposedRange.addMappingInformation(info, ConsumerUtils.emptyConsumer())); - return Collections.singletonList(newComposedRange); + return Collections.singletonList( + composeOnMethodSignature(existingClassBuilder, newRange, lastExistingRange)); } - ExistingMapping mappedRangesForPosition = computeExistingMapping(existingRanges); + ExistingMappings mappedRangesForPosition = ExistingMappings.create(existingRanges); List newComposedRanges = new ArrayList<>(); assert newRange.minifiedRange != null; - // First check if the original range matches the existing minified range. - List existingMappedRanges = - mappedRangesForPosition.getMappedRangesForPosition( - newRange.getFirstPositionOfOriginalRange(NO_RANGE_FROM)); - MappedRange lastExistingMappedRange = ListUtils.lastOrNull(existingMappedRanges); - int startingPosition = newRange.minifiedRange.from; - if (existingMappedRanges == null) { - // If we cannot lookup the original position because it has been removed we compose with - // the existing method signature. - if (newRange.isOriginalRangePreamble() - || (existingRanges.size() == 1 && lastExistingRange.minifiedRange == null)) { - return Collections.singletonList( - new MappedRange( - newRange.minifiedRange, - potentiallyQualifySignature( - newRange.signature, - lastExistingRange.signature, - existingClassBuilder.getOriginalName()), - lastExistingRange.originalRange != null - && lastExistingRange.originalRange.span() == 1 - ? lastExistingRange.originalRange - : EMPTY_RANGE, - newRange.renamedName)); - } else if (newRange.getOriginalRangeOrIdentity().from == 0) { - // Similar to the trick below we create a synthetic range to map the preamble to. - Pair emptyRange = - createEmptyRange( - newRange, lastExistingRange, mappedRangesForPosition, startingPosition); - lastExistingMappedRange = emptyRange.getSecond(); - existingMappedRanges = Collections.singletonList(emptyRange.getSecond()); - startingPosition += emptyRange.getFirst() + 1; - } else { - throw new MappingComposeException( - "Unexpected missing original position for '" + newRange + "'."); + int minifiedStart = newRange.minifiedRange.from; + int minifiedEnd = newRange.minifiedRange.to; + int originalLineNumberEnd = newRange.getOriginalLineNumber(minifiedEnd); + while (minifiedStart <= minifiedEnd) { + int originalLineNumber = newRange.getOriginalLineNumber(minifiedStart); + // First query if there is an interval ranging over the minifiedStart: + // [--existing--] + // [minifiedStart----minifiedEnd] + // Note that if existing mapped range has no line information, it will start at position -1. + List existingRangesForPosition = + mappedRangesForPosition.getPreviousRanges(originalLineNumber); + // If we find no previous ranges or the previous range is outside the minified start, we + // query for an interval defined later: + // [--existing--] + // [minifiedStart----minifiedEnd] + if (isRangeNullOrBefore(existingRangesForPosition, originalLineNumber)) { + existingRangesForPosition = mappedRangesForPosition.getNextRanges(originalLineNumber); } - } - assert lastExistingMappedRange != null; - // If the existing mapped minified range is equal to the original range of the new range - // then we have a perfect mapping that we can translate directly. - if (lastExistingMappedRange.minifiedRange.equals(newRange.getOriginalRangeOrIdentity())) { - computeComposedMappedRange( - existingClassBuilder, - newComposedRanges, - newRange, - existingMappedRanges, - computedOutlineInformation, - newRange.minifiedRange.from, - newRange.minifiedRange.to); - return newComposedRanges; - } - // Otherwise, we have a situation where the minified range references over multiple - // existing ranges. We simply chop op when the range changes on the right hand side. To - // ensure we do not mess up the spans from the original range, we have to check if the - // current starting position is inside an original range, and then chop it off. - // Similarly, when writing the last block, we have to cut it off to match. - int lastStartingMinifiedFrom = newRange.minifiedRange.from; - for (int position = startingPosition; position <= newRange.minifiedRange.to; position++) { - List existingMappedRangesForPosition = - mappedRangesForPosition.getMappedRangesForPosition( - newRange.getOriginalLineNumber(position)); - MappedRange lastExistingMappedRangeForPosition = - ListUtils.lastOrNull(existingMappedRangesForPosition); - if (lastExistingMappedRangeForPosition == null - || !lastExistingMappedRange.minifiedRange.equals( - lastExistingMappedRangeForPosition.minifiedRange)) { - // We have seen an existing range we have to compute a splitting for. - computeComposedMappedRange( - existingClassBuilder, - newComposedRanges, - newRange, - existingMappedRanges, - computedOutlineInformation, - lastStartingMinifiedFrom, - position - 1); - // Advance the last starting position to this point and advance the existing mapped - // ranges for this position. - lastStartingMinifiedFrom = position; - if (existingMappedRangesForPosition == null) { - if (!options.mappingComposeOptions().allowNonExistingOriginalRanges) { - throw new MappingComposeException( - "Unexpected missing original position for '" + newRange + "'."); - } - // We are at the first position of a hole. If we have existing ranges: - // 1:1:void foo():41:41 -> a - // 9:9:void foo():49:49 -> a - // We may have a new range that is: - // 21:29:void foo():1:9 - // We end up here at position 2 and we have already committed - // 21:21:void foo():41:41. - // We then introduce a "fake" normal range to simulate the result of retracing one after - // the other to end up with: - // 21:21:void foo():41:41 - // 22:28:void foo():2:8 - // 29:29:void foo():49:49. - Pair emptyRange = - createEmptyRange( - newRange, - lastExistingMappedRange, - mappedRangesForPosition, - newRange.getOriginalLineNumber(position)); - lastExistingMappedRange = emptyRange.getSecond(); - existingMappedRanges = Collections.singletonList(emptyRange.getSecond()); - position += emptyRange.getFirst(); + // Check if we have the case: + // [--existing--] [--existing--] + // [minifiedStart----minifiedEnd] + // Where the previous existing interval is not the special one defined on -1. + if (isRangeNullOrAfter(existingRangesForPosition, originalLineNumberEnd)) { + Range minifiedRange = new Range(minifiedStart, minifiedEnd); + newComposedRanges.add(newRange.copyWithNewRanges(minifiedRange, minifiedRange)); + minifiedStart = minifiedEnd + 1; + } else { + // Check if we have an existing mapped range without line information. + MappedRange lastMappedRange = ListUtils.last(existingRangesForPosition); + if (lastMappedRange.minifiedRange == null) { + return Collections.singletonList( + composeOnMethodSignature(existingClassBuilder, newRange, lastExistingRange)); + } + // If we have the case: + // [--existing--] + // [minifiedStart----minifiedEnd] + // We have to insert a mapped range from minifiedStart to existingStart. + if (originalLineNumber < lastMappedRange.minifiedRange.from) { + int tempMinifiedTo = + minifiedStart + (lastMappedRange.minifiedRange.from - originalLineNumber - 1); + Range minifiedRange = new Range(minifiedStart, tempMinifiedTo); + Range originalRange = + new Range(originalLineNumber, lastMappedRange.minifiedRange.from - 1); + newComposedRanges.add( + newRange.copyWithNewRanges( + minifiedRange, originalRange.isPreamble() ? originalRange : minifiedRange)); + minifiedStart = tempMinifiedTo + 1; } else { - lastExistingMappedRange = lastExistingMappedRangeForPosition; - existingMappedRanges = existingMappedRangesForPosition; + // Otherwise we have the case where we can compose directly up until the first end. If + // the starting point matches directly, we've found the range when looking up + // previousRanges since interval search is inclusive, the intervals are disjoint and the + // interval is registered at minifiedStart. + // [--existing--] + // [minifiedStart----minifiedEnd] + int span = computeSpan(minifiedStart, minifiedEnd, newRange, lastMappedRange); + composeMappedRange( + existingClassBuilder, + newComposedRanges, + newRange, + existingRangesForPosition, + computedOutlineInformation, + minifiedStart, + minifiedStart + span); + minifiedStart += span + 1; } } } - computeComposedMappedRange( - existingClassBuilder, - newComposedRanges, - newRange, - existingMappedRanges, - computedOutlineInformation, - lastStartingMinifiedFrom, - newRange.minifiedRange.to); return newComposedRanges; } - private Pair createEmptyRange( - MappedRange newRange, - MappedRange lastExistingMappedRange, - ExistingMapping mappedRangesForPosition, - int position) { - int startOriginalPosition = newRange.getOriginalLineNumber(position); - Integer endOriginalPosition = - mappedRangesForPosition.getCeilingForPosition(startOriginalPosition); - if (endOriginalPosition == null) { - endOriginalPosition = newRange.getLastPositionOfOriginalRange(); - } else if (endOriginalPosition > startOriginalPosition) { - endOriginalPosition = endOriginalPosition - 1; - } - Range newOriginalRange = new Range(startOriginalPosition, endOriginalPosition); - MappedRange nonExistingMappedRange = - new MappedRange( - newOriginalRange, - lastExistingMappedRange.getOriginalSignature().asMethodSignature(), - newOriginalRange, - lastExistingMappedRange.getRenamedName()); - nonExistingMappedRange.setResidualSignatureInternal( - lastExistingMappedRange.getResidualSignature()); - return Pair.create((endOriginalPosition - startOriginalPosition), nonExistingMappedRange); + private boolean isRangeNullOrBefore( + List existingRangesForPosition, int originalLineNumber) { + return existingRangesForPosition == null + || existingRangesForPosition.isEmpty() + || (ListUtils.last(existingRangesForPosition).minifiedRange != null + && ListUtils.last(existingRangesForPosition).minifiedRange.to < originalLineNumber); } - public interface ExistingMapping { + private boolean isRangeNullOrAfter( + List existingRangesForPosition, int originalLineNumberEnd) { + return existingRangesForPosition == null + || (ListUtils.last(existingRangesForPosition).minifiedRange != null + && originalLineNumberEnd + < ListUtils.last(existingRangesForPosition).minifiedRange.from); + } - Integer getCeilingForPosition(int i); + private int computeSpan( + int minifiedStart, int minifiedEnd, MappedRange newRange, MappedRange lastMappedRange) { + int minifiedSpan = minifiedEnd - minifiedStart; + if (minifiedSpan >= 1 && newRange.getOriginalRangeOrIdentity().isSingleLine()) { + return minifiedSpan; + } + return Math.min( + minifiedSpan, + lastMappedRange.minifiedRange.to - newRange.getOriginalLineNumber(minifiedStart)); + } - List getMappedRangesForPosition(int i); + private MappedRange composeOnMethodSignature( + ComposingClassBuilder existingClassBuilder, + MappedRange newRange, + MappedRange lastExistingRange) + throws MappingComposeException { + Range originalRange; + if (newRange.getOriginalRangeOrIdentity() == null) { + originalRange = null; + } else { + originalRange = + lastExistingRange.originalRange != null + && lastExistingRange.originalRange.isSingleLine() + ? lastExistingRange.originalRange + : EMPTY_RANGE; + } + MappedRange newComposedRange = + new MappedRange( + newRange.minifiedRange, + potentiallyQualifySignature( + newRange.signature, + lastExistingRange.signature, + existingClassBuilder.getOriginalName()), + originalRange, + newRange.renamedName); + composeMappingInformation( + newComposedRange.getAdditionalMappingInformation(), + lastExistingRange.getAdditionalMappingInformation(), + info -> newComposedRange.addMappingInformation(info, ConsumerUtils.emptyConsumer())); + return newComposedRange; } /*** - * Builds a position to mapped ranges for mappings by looking up all mapped ranges for a given - * position. + * Builds a range to mapped ranges for mappings by building a lookup table sorted on the + * starting coordinate. */ - private ExistingMapping computeExistingMapping(List existingRanges) { - TreeMap> mappedRangesForPosition = new TreeMap<>(); - List currentRangesForPosition = new ArrayList<>(); - int startExisting = NO_RANGE_FROM; - int endExisting = NO_RANGE_FROM; - boolean isCatchAll = false; - for (int i = 0; i < existingRanges.size(); i++) { - MappedRange mappedRange = existingRanges.get(i); - currentRangesForPosition.add(mappedRange); - if (!isInlineMappedRange(existingRanges, i)) { - if (startExisting == NO_RANGE_FROM) { - startExisting = mappedRange.getFirstPositionOfOriginalRange(NO_RANGE_FROM); - } - endExisting = Math.max(mappedRange.getLastPositionOfOriginalRange(), endExisting); - if (mappedRange.minifiedRange == null) { - mappedRangesForPosition.put(NO_RANGE_FROM, currentRangesForPosition); - } else if (mappedRange.minifiedRange.isCatchAll()) { - isCatchAll = true; + private static class ExistingMappings { + + private final TreeMap> mappedRangesForPosition; + + private ExistingMappings(TreeMap> mappedRangesForPosition) { + this.mappedRangesForPosition = mappedRangesForPosition; + } + + private static ExistingMappings create(List existingRanges) { + TreeMap> mappedRangesForPosition = new TreeMap<>(); + if (!existingRanges.isEmpty()) { + ImmutableList.Builder builder = ImmutableList.builder(); + Range lastRange = existingRanges.get(0).minifiedRange; + if (lastRange == null) { + assert existingRanges.size() == 1; + mappedRangesForPosition.put( + NO_RANGE_FROM, Collections.singletonList(existingRanges.get(0))); } else { - for (int position = mappedRange.minifiedRange.from; - position <= mappedRange.minifiedRange.to; - position++) { - mappedRangesForPosition.put(position, currentRangesForPosition); + for (MappedRange mappedRange : existingRanges) { + if (!lastRange.equals(mappedRange.minifiedRange)) { + mappedRangesForPosition.put(lastRange.from, builder.build()); + builder = new ImmutableList.Builder<>(); + lastRange = mappedRange.minifiedRange; + } + builder.add(mappedRange); } + mappedRangesForPosition.put(lastRange.from, builder.build()); } - currentRangesForPosition = new ArrayList<>(); } + return new ExistingMappings(mappedRangesForPosition); + } + + private List getNextRanges(int startPosition) { + Integer ceilingKey = mappedRangesForPosition.ceilingKey(startPosition); + return ceilingKey == null ? null : mappedRangesForPosition.get(ceilingKey); } - boolean finalIsCatchAll = isCatchAll; - List finalCurrentRangesForPosition = currentRangesForPosition; - return new ExistingMapping() { - @Override - public Integer getCeilingForPosition(int i) { - return finalIsCatchAll ? i : mappedRangesForPosition.ceilingKey(i); - } - @Override - public List getMappedRangesForPosition(int i) { - return finalIsCatchAll ? finalCurrentRangesForPosition : mappedRangesForPosition.get(i); + private List getPreviousRanges(int startPosition) { + Integer floorKey = mappedRangesForPosition.floorKey(startPosition); + return floorKey == null ? null : mappedRangesForPosition.get(floorKey); } - }; } - private void computeComposedMappedRange( + private void composeMappedRange( ComposingClassBuilder existingClassBuilder, List newComposedRanges, MappedRange newMappedRange, diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java index 5ddf11e439..73872826ef 100644 --- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java +++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java @@ -415,6 +415,10 @@ public MethodSignature toUnqualified() { return new MethodSignature(toUnqualifiedName(), type, parameters); } + public MethodSignature toUnqualifiedIfQualified() { + return isQualified() ? new MethodSignature(toUnqualifiedName(), type, parameters) : this; + } + public DexMethod toDexMethod(DexItemFactory factory, DexType clazz) { DexType[] paramTypes = new DexType[parameters.length]; for (int i = 0; i < parameters.length; i++) { diff --git a/src/main/java/com/android/tools/r8/naming/Range.java b/src/main/java/com/android/tools/r8/naming/Range.java index 5c0714b1f8..3156ea7a3f 100644 --- a/src/main/java/com/android/tools/r8/naming/Range.java +++ b/src/main/java/com/android/tools/r8/naming/Range.java @@ -58,6 +58,10 @@ public int span() { return (to - from) + 1; } + public boolean isSingleLine() { + return isCardinal || to == from; + } + @Override public int hashCode() { return Objects.hash(from, to, isCardinal); diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleSyntheticTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleSyntheticTest.java index 71c344d596..aab1805293 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleSyntheticTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleSyntheticTest.java @@ -40,7 +40,7 @@ public ComposePreambleSyntheticTest(TestParameters parameters) { "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "a -> b:", " 1:2:void lambda$0(boolean):0:1 ->" + " lambda$0$com-r8-Base", - " 1:2:void" + " lambda$0$com-r8-Base(boolean):0" + " -> lambda$0$com-r8-Base", + " 1:2:void lambda$0$com-r8-Base(boolean):0 -> lambda$0$com-r8-Base", " # {'id':'com.android.tools.r8.synthesized'}"); private static final String mappingResult = StringUtils.unixLines( @@ -48,9 +48,9 @@ public ComposePreambleSyntheticTest(TestParameters parameters) { "com.foo -> b:", " 1:1:void lambda$0(boolean):0:0 -> lambda$0$com-r8-Base", " 1:1:void lambda$0$com-r8-Base(boolean):0:0 -> lambda$0$com-r8-Base", + " # {'id':'com.android.tools.r8.synthesized'}", " 2:2:void lambda$0(boolean):355:355 -> lambda$0$com-r8-Base", - " 2:2:void lambda$0$com-r8-Base(boolean):0:0 -> lambda$0$com-r8-Base", - " # {'id':'com.android.tools.r8.synthesized'}"); + " 2:2:void lambda$0$com-r8-Base(boolean):0:0 -> lambda$0$com-r8-Base"); @Test public void testCompose() throws Exception { diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleTest.java index 57b70a01a0..f9a16e85f2 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleTest.java @@ -46,7 +46,7 @@ public ComposePreambleTest(TestParameters parameters) { StringUtils.unixLines( "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "com.foo -> b:", - " 1:1:int f1():0:0 -> h1", + " 1:1:int g1():0:0 -> h1", " 2:3:int f1():41:42 -> h1", " 0:65535:void f2():112:112 -> h2"); diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeWithHolesTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeWithHolesTest.java new file mode 100644 index 0000000000..2445d9c3ee --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeWithHolesTest.java @@ -0,0 +1,58 @@ +// Copyright (c) 2023, 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.mappingcompose; + +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ComposeWithHolesTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "com.foo -> A:", + " 2:11:java.lang.String internalToDescriptor(java.lang.String,boolean):45:54 -> a", + " 72:72:java.lang.String internalToDescriptor(java.lang.String,boolean):56:56 -> a"); + private static final String mappingBar = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "A -> B:", + " 1:71:java.lang.String a(java.lang.String,boolean):2:72 -> e"); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "com.foo -> B:", + " 1:10:java.lang.String internalToDescriptor(java.lang.String,boolean):45:54 -> e", + " 11:70:java.lang.String a(java.lang.String,boolean) -> e", + " 71:71:java.lang.String internalToDescriptor(java.lang.String,boolean):56:56 -> e"); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); + } +} From 789ab76e50b925cb70e05a3f4f74bdb7d6713c06 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Thu, 8 Jun 2023 10:45:51 +0200 Subject: [PATCH 054/153] Update kotlinx-metadata-jvm to 0.6.2 Bug: b/286030996 Change-Id: I437c264437213fa27c54a5c129aaea32f73b06da --- build.gradle | 2 +- third_party/dependencies.tar.gz.sha1 | 2 +- third_party/dependencies_new.tar.gz.sha1 | 2 +- tools/create_local_maven_with_dependencies.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 8a0cc1f03b..3c67f45726 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ ext { // The kotlin version is only here to specify the kotlin language level, // all kotlin compilations are done in tests. kotlinVersion = '1.8.0' - kotlinExtMetadataJVMVersion = '0.6.0' + kotlinExtMetadataJVMVersion = '0.6.2' smaliVersion = '3.0.3' errorproneVersion = '2.18.0' testngVersion = '6.10' diff --git a/third_party/dependencies.tar.gz.sha1 b/third_party/dependencies.tar.gz.sha1 index cf12fc22ae..aa1e6a71eb 100644 --- a/third_party/dependencies.tar.gz.sha1 +++ b/third_party/dependencies.tar.gz.sha1 @@ -1 +1 @@ -76c51489d87c284cea0e73646c5cc45a9ffc3665 \ No newline at end of file +ca36d85787d7df692bc383cfad225580f1a9cece \ No newline at end of file diff --git a/third_party/dependencies_new.tar.gz.sha1 b/third_party/dependencies_new.tar.gz.sha1 index 54a7c72aac..5d89edc995 100644 --- a/third_party/dependencies_new.tar.gz.sha1 +++ b/third_party/dependencies_new.tar.gz.sha1 @@ -1 +1 @@ -08dbd497e182be658628252f0bb890894cc88dcc \ No newline at end of file +37f0c05fb0b91effe962a2dde30818c6e5e3cb23 \ No newline at end of file diff --git a/tools/create_local_maven_with_dependencies.py b/tools/create_local_maven_with_dependencies.py index 296cdf0baf..00f10d83d9 100755 --- a/tools/create_local_maven_with_dependencies.py +++ b/tools/create_local_maven_with_dependencies.py @@ -21,7 +21,7 @@ ASM_VERSION = '9.5' ESPRESSO_VERSION = '3.0.0' FASTUTIL_VERSION = '7.2.1' -KOTLIN_METADATA_VERSION = '0.6.0' +KOTLIN_METADATA_VERSION = '0.6.2' KOTLIN_VERSION = '1.8.0' GUAVA_VERSION = '31.1-jre' GSON_VERSION = '2.7' From 03c520c260dde6560d7d1ea0f02c55f25de37642 Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Fri, 9 Jun 2023 11:17:26 +0200 Subject: [PATCH 055/153] Make errorprone happy with argument order Change-Id: Ia4ff31b986c8f749594b1532fa30d8d6a9114c99 --- .../com/android/tools/r8/naming/ComposingBuilder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index 716c7cdd75..0ccde4760f 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -657,9 +657,9 @@ private void composeMethodNamings( composedRanges = fixupOutlineInformation( computedOutlineInformation, - memberNaming.getOriginalSignature().asMethodSignature(), newRangesToCompose, - composedRanges); + composedRanges, + memberNaming.getOriginalSignature().asMethodSignature()); MethodSignature residualSignature = memberNaming .computeResidualSignature(type -> inverseClassMapping.getOrDefault(type, type)) @@ -680,9 +680,9 @@ private void composeMethodNamings( private List fixupOutlineInformation( ComputedOutlineInformation computedOutlineInformation, - MethodSignature originalSignature, List newRangesToBeComposed, - List composedRanges) + List composedRanges, + MethodSignature originalSignature) throws MappingComposeException { composedRanges = fixupInlinedOutlines(computedOutlineInformation, composedRanges); fixupOutlineInformation(computedOutlineInformation, newRangesToBeComposed, composedRanges); From bbd36a090af3001cda3020b5dd89b25f63d7c19a Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Fri, 9 Jun 2023 12:37:50 +0200 Subject: [PATCH 056/153] Script to check for holes in cherry-picks to release branches Bug: b/286177952 Change-Id: Ie1141bf59f68c20d1fb6ba230963c7f9babc7130 --- tools/check-cherry-picks.py | 184 ++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100755 tools/check-cherry-picks.py diff --git a/tools/check-cherry-picks.py b/tools/check-cherry-picks.py new file mode 100755 index 0000000000..adf763cc9b --- /dev/null +++ b/tools/check-cherry-picks.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023, 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. + +import subprocess +import sys +import re +import r8_release + +class Branch: + def __init__(self, name, first, last=None): + self.name = name + self.first = first + self.last = last # optional last for testing purposes. + + def origin(self): + return "origin/%s" % self.name + + def last_or_origin(self): + return self.last if self.last else self.origin() + + def __str__(self): + return self.name + +# The initial commit is the furthest back we need to search on main. +# Currently, it is the merge point of main onto 4.0.23-dev +MAIN = Branch('main', 'a2e203580aa00a36f85cd68d3d584b97aef34d59') +OLDEST_BRANCH_VERSION = (4, 0) +DEV_BRANCH_VERSION = [int(s) for s in r8_release.R8_DEV_BRANCH.split('.')] + +# List of change ids that should not be reported. +IGNORED = [ + 'I92d7bf3afbf609fdea21683941cfd15c90305cf2' +] + +VERBOSE = False + +# Helper to call and decode a shell command. +def run_cmd(cmd): + if VERBOSE: + print(' '.join(cmd)) + return subprocess.check_output(cmd).decode('UTF-8') + +# Comparator on major and minor branch versions. +def branch_version_less_than(b1, b2): + if b1[0] < b2[0]: + return True + if b1[0] == b2[0] and b1[1] < b2[1]: + return True + return False + +# Find all release branches between OLDEST_BRANCH and DEV_BRANCH +def get_release_branches(): + # Release branches are assumed to be of the form 'origin/X.Y' + out = run_cmd(['git', 'branch', '-r', '-l']) + pattern = re.compile('origin/(\d+).(\d+)') + releases = [] + for line in out.split('\n'): + m = pattern.search(line.strip()) + if m: + major = m.group(1) + minor = m.group(2) + if major and minor: + candidate = (int(major), int(minor)) + if branch_version_less_than(candidate, OLDEST_BRANCH_VERSION): + continue + if branch_version_less_than(candidate, DEV_BRANCH_VERSION): + releases.extend(find_dev_cutoff(candidate)) + return releases + +# Find the most recent commit hash that is for a -dev version. +# This is the starting point for the map of commits after cutoff from main. +def find_dev_cutoff(branch_version): + out = run_cmd([ + 'git', + 'log', + 'origin/%d.%d' % branch_version, + '--grep', 'Version .*-dev', + '--pretty=oneline', + ]) + # Format of output is: Version -dev + try: + hash = out[0:out.index(' ')] + return [Branch('%d.%d' % branch_version, hash)] + except ValueError: + throw_error("Failed to find dev cutoff for branch %d.%d" % branch_version) + +# Build a map from each "Change-Id" hash to the hash of its commit. +def get_change_id_map(branch): + out = run_cmd([ + 'git', + 'log', + '%s..%s' % (branch.first, branch.last_or_origin()) + ]) + map = {} + current_commit = None + for line in out.split('\n'): + if line.startswith('commit '): + current_commit = line[len('commit '):] + assert len(current_commit) == 40 + elif line.strip().startswith('Change-Id: '): + change_id = line.strip()[len('Change-Id: '):] + assert len(change_id) == 41 + map[change_id] = current_commit + return map + +# Check if a specific commit is present on a specific branch. +def is_commit_in(commit, branch): + out = run_cmd(['git', 'branch', '-r', branch.origin(), '--contains', commit]) + return out.strip() == branch.origin() + +def main(): + found_errors = False + # The main map is all commits back to the "init" point. + main_map = get_change_id_map(MAIN) + # Compute the release branches. + release_branches = get_release_branches() + # Populate the release maps with all commits after the last -dev point. + release_maps = {} + for branch in release_branches: + release_maps[branch.name] = get_change_id_map(branch) + # Each branch is then compared forwards with each subsequent branch. + for i in range(len(release_branches)): + branch = release_branches[i] + newer_branches = release_branches[i+1:] + if (len(newer_branches) == 0): + print('Last non-dev release branch is %s, nothing to check.' % branch) + continue + print('Checking branch %s.' % branch) + changes = release_maps[branch.name] + cherry_picks_count = 0 + for change in changes.keys(): + is_cherry_pick = False + missing_from = None + commit_on_main = main_map.get(change) + for newer_branch in newer_branches: + if change in release_maps[newer_branch.name]: + is_cherry_pick = True + # If the change is in the release mappings check for holes. + if missing_from: + found_errors = change_error( + change, + 'Error: missing Change-Id %s on branch %s. ' + 'Is present on %s and again on %s.' % ( + change, missing_from, branch, newer_branch, + )) + elif commit_on_main: + is_cherry_pick = True + # The change is not in the non-dev part of the branch, so we need to + # check that the fork from main included the change. + if not is_commit_in(commit_on_main, newer_branch): + found_errors = change_error( + change, + 'Error: missing Change-Id %s on branch %s. ' + 'Is present on %s and on main as commit %s.' % ( + change, newer_branch, branch, commit_on_main + )) + else: + # The change is not on "main" so we just record for holes on releases. + missing_from = newer_branch + if is_cherry_pick: + cherry_picks_count += 1 + print('Found %d cherry-picks (out of %d commits).' % ( + cherry_picks_count, len(changes))) + + if found_errors: + return 1 + return 0 + +def change_error(change, msg): + if change in IGNORED: + return False + error(msg) + return True + +def throw_error(msg): + raise ValueError(msg) + +def error(msg): + print(msg, file=sys.stderr) + +if __name__ == '__main__': + sys.exit(main()) From ff1c1b43035768dbd96fee6c36391e3f9c892d53 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 12:59:55 +0200 Subject: [PATCH 057/153] Mark methods obsolete after optimizations Change-Id: If57dbdf8afe7254d0912b706f4a109d6d742bf32 --- .../com/android/tools/r8/graph/AppView.java | 10 ++++- .../horizontalclassmerging/ClassMerger.java | 9 ++++- .../HorizontalClassMerger.java | 25 +++++++++---- .../InstanceInitializerMerger.java | 7 ++++ .../InstanceInitializerMergerCollection.java | 4 ++ .../code/ClassInitializerMerger.java | 17 +++++++-- .../optimize/enums/EnumUnboxingTreeFixer.java | 2 + .../ArgumentPropagatorApplicationFixer.java | 37 ++++++++++--------- 8 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index cc0041b607..83ea812bef 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -936,7 +936,10 @@ private static void rewriteWithLensAndApplication( timing.begin("Rewrite AppView"); boolean changed = appView.setGraphLens(lens); - assert changed; + + // Verify that the lens changed, except in the horizontal class merger case, where we install + // the lens prior to lens rewriting AppView. + assert changed || lens.isHorizontalClassMergerGraphLens(); assert application.verifyWithLens(appView.appInfo().app().asDirect(), lens); // The application has already been rewritten with the given applied lens. Therefore, we @@ -1051,7 +1054,10 @@ public void rewriteWithD8Lens(NonIdentityGraphLens lens, Timing timing) { private static void rewriteWithD8Lens( NonIdentityGraphLens lens, Timing timing, AppView appView) { boolean changed = appView.setGraphLens(lens); - assert changed; + + // Verify that the lens changed, except in the horizontal class merger case, where we install + // the lens prior to lens rewriting AppView. + assert changed || lens.isHorizontalClassMergerGraphLens(); appView.setArtProfileCollection( appView.getArtProfileCollection().rewrittenWithLens(appView, lens, timing)); diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java index 459cf9ad41..efbfad47cc 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java @@ -114,6 +114,10 @@ void mergeDirectMethods( profileCollectionAdditions, syntheticArgumentClass, syntheticInitializerConverterBuilder); mergeStaticClassInitializers(syntheticInitializerConverterBuilder); group.forEach(this::mergeDirectMethods); + if (!classInitializerMerger.isEmpty() && classInitializerMerger.isTrivialMerge()) { + classInitializerMerger.setObsolete(); + } + instanceInitializerMergers.setObsolete(); } void mergeStaticClassInitializers( @@ -165,7 +169,10 @@ void mergeDirectMethods(DexProgramClass toMerge) { if (!classMethodsBuilder.isFresh(newMethod)) { newMethod = renameDirectMethod(method); } - classMethodsBuilder.addDirectMethod(definition.toTypeSubstitutedMethod(newMethod)); + classMethodsBuilder.addDirectMethod( + newMethod != method.getReference() + ? definition.toTypeSubstitutedMethod(newMethod) + : method.getDefinition()); if (definition.getReference() != newMethod) { lensBuilder.moveMethod(definition.getReference(), newMethod); } diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java index 746dbd5896..3fa5ee2374 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java @@ -162,6 +162,21 @@ private void run( assert verifyNoCyclesInInterfaceHierarchies(appView, groups); + FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier = null; + if (mode.isInitial()) { + fieldAccessInfoCollectionModifier = createFieldAccessInfoCollectionModifier(groups); + } else { + assert groups.stream().noneMatch(MergeGroup::hasClassIdField); + } + + // Set the new graph lens before finalizing any synthetic code. + appView.setGraphLens(horizontalClassMergerGraphLens); + codeProvider.setGraphLens(horizontalClassMergerGraphLens); + + // Finalize synthetic code. + transformIncompleteCode(groups, horizontalClassMergerGraphLens, executorService); + syntheticInitializerConverter.convertInstanceInitializers(executorService); + // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation // sites, fields accesses, etc. are correctly transferred to the target classes. DexApplication newApplication = getNewApplication(mergedClasses); @@ -188,7 +203,6 @@ private void run( .rewrittenWithLens(syntheticItems, horizontalClassMergerGraphLens, timing))); appView.rewriteWithD8Lens(horizontalClassMergerGraphLens, timing); } - codeProvider.setGraphLens(horizontalClassMergerGraphLens); // Amend art profile collection. profileCollectionAdditions @@ -197,15 +211,10 @@ private void run( .commit(appView); // Record where the synthesized $r8$classId fields are read and written. - if (mode.isInitial()) { - createFieldAccessInfoCollectionModifier(groups).modify(appView.withLiveness()); - } else { - assert groups.stream().noneMatch(MergeGroup::hasClassIdField); + if (fieldAccessInfoCollectionModifier != null) { + fieldAccessInfoCollectionModifier.modify(appView.withLiveness()); } - transformIncompleteCode(groups, horizontalClassMergerGraphLens, executorService); - syntheticInitializerConverter.convertInstanceInitializers(executorService); - appView.pruneItems( prunedItems.toBuilder().setPrunedApp(appView.app()).build(), executorService); diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java index 0cf3a94e21..62def22399 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java @@ -412,6 +412,13 @@ void merge( } } + void setObsolete() { + if (hasInstanceInitializerDescription() || !useSyntheticMethod()) { + instanceInitializers.forEach( + instanceInitializer -> instanceInitializer.getDefinition().setObsolete()); + } + } + private boolean useSyntheticMethod() { return !isSingleton() || group.hasClassIdField(); } diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java index 952421efd4..4ce1f2fdd2 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java @@ -141,4 +141,8 @@ public void forEach(Consumer consumer) { instanceInitializerMergers.forEach(consumer); equivalentInstanceInitializerMergers.values().forEach(consumer); } + + public void setObsolete() { + forEach(InstanceInitializerMerger::setObsolete); + } } diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java index 6a030d72a6..09e9911ae4 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java @@ -75,9 +75,8 @@ public boolean isEmpty() { } public Code getCode(DexMethod syntheticMethodReference) { - assert !classInitializers.isEmpty(); - ProgramMethod firstClassInitializer = ListUtils.first(classInitializers); - if (firstClassInitializer.getDefinition().getCode().isCfCode()) { + assert !isEmpty(); + if (isTrivialMerge()) { assert IterableUtils.allIdentical( classInitializers, classInitializer -> classInitializer.getDefinition().getCode().isCfCode()); @@ -100,6 +99,11 @@ public CfVersion getCfVersion() { return null; } + public boolean isTrivialMerge() { + ProgramMethod firstClassInitializer = ListUtils.first(classInitializers); + return firstClassInitializer.getDefinition().getCode().isCfCode(); + } + public ComputedApiLevel getApiReferenceLevel(AppView appView) { assert !classInitializers.isEmpty(); return ListUtils.fold( @@ -108,6 +112,10 @@ public ComputedApiLevel getApiReferenceLevel(AppView appView) { (accApiLevel, method) -> accApiLevel.max(method.getDefinition().getApiLevel())); } + public void setObsolete() { + classInitializers.forEach(classInitializer -> classInitializer.getDefinition().setObsolete()); + } + public static class Builder { private final ImmutableList.Builder classInitializers = ImmutableList.builder(); @@ -182,7 +190,7 @@ private void addCfCode( * Provides a piece of {@link IRCode} that is the concatenation of a collection of class * initializers. */ - private static class IRProvider extends Code { + static class IRProvider extends Code { private final ImmutableList classInitializers; private final DexMethod syntheticMethodReference; @@ -261,6 +269,7 @@ public IRCode buildIR( callerPosition, classInitializer.getOrigin(), RewrittenPrototypeDescription.none()); + classInitializer.getDefinition().setObsolete(); DexProgramClass downcast = null; instructionIterator.previous(); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java index 940b63975c..eaa1d40bad 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java @@ -583,8 +583,10 @@ private void processMethod( && enumDataMap.representativeType(method.getHolderType()) != method.getHolderType()) { assert method.getDefinition().getCode().isEmptyVoidMethod(); prunedItemsBuilder.addRemovedMethod(method.getReference()); + method.getDefinition().setObsolete(); } else if (method.getDefinition().isInstanceInitializer()) { prunedItemsBuilder.addRemovedMethod(method.getReference()); + method.getDefinition().setObsolete(); } else if (method.getDefinition().isNonPrivateVirtualMethod()) { nonPrivateVirtualMethods.add(method.getReference()); } else { diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java index 4ad22f3a0b..4441690613 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java @@ -100,23 +100,26 @@ private void fixupMethods(DexProgramClass clazz) { return method; } - return method.toTypeSubstitutedMethod( - methodReferenceAfterParameterRemoval, - builder -> { - if (graphLens.hasPrototypeChanges(methodReferenceAfterParameterRemoval)) { - RewrittenPrototypeDescription prototypeChanges = - graphLens.getPrototypeChanges(methodReferenceAfterParameterRemoval); - builder - .apply(prototypeChanges.createParameterAnnotationsRemover(method)) - .setGenericSignature(MethodTypeSignature.noSignature()); - if (method.isInstance() - && prototypeChanges.getArgumentInfoCollection().isArgumentRemoved(0)) { - builder - .modifyAccessFlags(flags -> flags.demoteFromFinal().promoteToStatic()) - .unsetIsLibraryMethodOverride(); - } - } - }); + DexEncodedMethod replacement = + method.toTypeSubstitutedMethod( + methodReferenceAfterParameterRemoval, + builder -> { + if (graphLens.hasPrototypeChanges(methodReferenceAfterParameterRemoval)) { + RewrittenPrototypeDescription prototypeChanges = + graphLens.getPrototypeChanges(methodReferenceAfterParameterRemoval); + builder + .apply(prototypeChanges.createParameterAnnotationsRemover(method)) + .setGenericSignature(MethodTypeSignature.noSignature()); + if (method.isInstance() + && prototypeChanges.getArgumentInfoCollection().isArgumentRemoved(0)) { + builder + .modifyAccessFlags(flags -> flags.demoteFromFinal().promoteToStatic()) + .unsetIsLibraryMethodOverride(); + } + } + }); + method.setObsolete(); + return replacement; }); } From 1c1ce4099eaf27101c25b295506e2ec97792fdb2 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 13:00:06 +0200 Subject: [PATCH 058/153] Parallelize lens rewriting of AppView Change-Id: I129f25242f5b54473b443f37153627a3797171de --- .../com/android/tools/r8/graph/AppView.java | 189 ++++++++++++++---- .../r8/graph/lens/NonIdentityGraphLens.java | 5 +- ...DefaultOpenClosedInterfacesCollection.java | 5 + ...onEmptyOpenClosedInterfacesCollection.java | 5 + .../OpenClosedInterfacesCollection.java | 2 + .../r8/shaking/AssumeInfoCollection.java | 4 + .../shaking/ProguardCompatibilityActions.java | 4 + .../android/tools/r8/utils/ArrayUtils.java | 5 + .../android/tools/r8/utils/ThreadUtils.java | 5 + .../com/android/tools/r8/utils/Timing.java | 18 +- .../tools/r8/utils/threads/FutureBox.java | 43 ++++ .../tools/r8/utils/threads/ThreadTask.java | 28 +++ .../r8/utils/threads/ThreadTaskUtils.java | 85 ++++++++ 13 files changed, 354 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/utils/threads/FutureBox.java create mode 100644 src/main/java/com/android/tools/r8/utils/threads/ThreadTask.java create mode 100644 src/main/java/com/android/tools/r8/utils/threads/ThreadTaskUtils.java diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index 83ea812bef..0d4e982f77 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -57,6 +57,8 @@ import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.ThrowingConsumer; import com.android.tools.r8.utils.Timing; +import com.android.tools.r8.utils.threads.ThreadTask; +import com.android.tools.r8.utils.threads.ThreadTaskUtils; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; import java.io.IOException; @@ -958,46 +960,153 @@ private static void rewriteWithLensAndApplication( newMemberRebindingLens, () -> { GraphLens appliedLensInModifiedLens = GraphLens.getIdentityLens(); - if (appView.hasLiveness()) { - appView - .withLiveness() - .setAppInfo( - appView.appInfoWithLiveness().rewrittenWithLens(application, lens, timing)); - } else { - assert appView.hasClassHierarchy(); - AppView appViewWithClassHierarchy = - appView.withClassHierarchy(); - AppInfoWithClassHierarchy appInfo = appViewWithClassHierarchy.appInfo(); - MainDexInfo rewrittenMainDexInfo = - appInfo - .getMainDexInfo() - .rewrittenWithLens(appView.getSyntheticItems(), lens, timing); - appViewWithClassHierarchy.setAppInfo( - appInfo.rebuildWithMainDexInfo(rewrittenMainDexInfo)); - } - appView.setAppServices(appView.appServices().rewrittenWithLens(lens, timing)); - appView.setArtProfileCollection( - appView.getArtProfileCollection().rewrittenWithLens(appView, lens, timing)); - appView.setAssumeInfoCollection( - appView - .getAssumeInfoCollection() - .rewrittenWithLens(appView, lens, appliedLensInModifiedLens, timing)); - if (appView.hasInitClassLens()) { - appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens, timing)); - } - if (appView.hasProguardCompatibilityActions()) { - appView.setProguardCompatibilityActions( - appView.getProguardCompatibilityActions().rewrittenWithLens(lens, timing)); - } - if (appView.hasMainDexRootSet()) { - appView.setMainDexRootSet(appView.getMainDexRootSet().rewrittenWithLens(lens, timing)); - } - appView.setOpenClosedInterfacesCollection( - appView.getOpenClosedInterfacesCollection().rewrittenWithLens(lens, timing)); - if (appView.hasRootSet()) { - appView.setRootSet(appView.rootSet().rewrittenWithLens(lens, timing)); - } - appView.setStartupProfile(appView.getStartupProfile().rewrittenWithLens(lens, timing)); + ThreadTaskUtils.processTasks( + executorService, + appView.options(), + timing + .beginMerger("Rewrite AppView concurrently", executorService) + .disableSlowestReporting(), + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + if (appView.hasLiveness()) { + appView + .withLiveness() + .setAppInfo( + appView + .appInfoWithLiveness() + .rewrittenWithLens(application, lens, threadTiming)); + } else { + assert appView.hasClassHierarchy(); + AppView appViewWithClassHierarchy = + appView.withClassHierarchy(); + AppInfoWithClassHierarchy appInfo = appViewWithClassHierarchy.appInfo(); + MainDexInfo rewrittenMainDexInfo = + appInfo + .getMainDexInfo() + .rewrittenWithLens(appView.getSyntheticItems(), lens, threadTiming); + appViewWithClassHierarchy.setAppInfo( + appInfo.rebuildWithMainDexInfo(rewrittenMainDexInfo)); + } + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + appView.setAppServices( + appView.appServices().rewrittenWithLens(lens, threadTiming)); + } + + @Override + public boolean shouldRun() { + return !appView.appServices().isEmpty(); + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + appView.setArtProfileCollection( + appView + .getArtProfileCollection() + .rewrittenWithLens(appView, lens, threadTiming)); + } + + @Override + public boolean shouldRun() { + return !appView.getArtProfileCollection().isEmpty(); + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + appView.setAssumeInfoCollection( + appView + .getAssumeInfoCollection() + .rewrittenWithLens( + appView, lens, appliedLensInModifiedLens, threadTiming)); + } + + @Override + public boolean shouldRun() { + return !appView.getAssumeInfoCollection().isEmpty(); + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + appView.setInitClassLens( + appView.initClassLens().rewrittenWithLens(lens, threadTiming)); + } + + @Override + public boolean shouldRun() { + return appView.hasInitClassLens(); + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + appView.setProguardCompatibilityActions( + appView + .getProguardCompatibilityActions() + .rewrittenWithLens(lens, threadTiming)); + } + + @Override + public boolean shouldRun() { + return appView.hasProguardCompatibilityActions() + && !appView.getProguardCompatibilityActions().isEmpty(); + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + appView.setMainDexRootSet( + appView.getMainDexRootSet().rewrittenWithLens(lens, threadTiming)); + } + + @Override + public boolean shouldRun() { + return appView.hasMainDexRootSet(); + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + appView.setOpenClosedInterfacesCollection( + appView + .getOpenClosedInterfacesCollection() + .rewrittenWithLens(lens, threadTiming)); + } + + @Override + public boolean shouldRun() { + return !appView.getOpenClosedInterfacesCollection().isEmpty(); + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + appView.setRootSet(appView.rootSet().rewrittenWithLens(lens, threadTiming)); + } + + @Override + public boolean shouldRun() { + return appView.hasRootSet(); + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { + appView.setStartupProfile( + appView.getStartupProfile().rewrittenWithLens(lens, threadTiming)); + } + + @Override + public boolean shouldRun() { + return !appView.getStartupProfile().isEmpty(); + } + }); }); timing.end(); // Rewrite AppView diff --git a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java index 8eaab04f57..ac9d814b2e 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java @@ -10,7 +10,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.code.InvokeType; -import com.android.tools.r8.utils.Action; +import com.android.tools.r8.utils.ThrowingAction; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; @@ -68,7 +68,8 @@ public final T return (found == null || stoppingCriterion.test(found)) ? null : found; } - public final void withAlternativeParentLens(GraphLens lens, Action action) { + public final void withAlternativeParentLens( + GraphLens lens, ThrowingAction action) throws E { GraphLens oldParent = getPrevious(); previousLens = lens; action.execute(); diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java index 9e3390b49d..7db0c3ad56 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java @@ -26,6 +26,11 @@ public boolean isDefinitelyClosed(DexClass clazz) { return false; } + @Override + public boolean isEmpty() { + return true; + } + @Override public OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens, Timing timing) { return this; diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java index b143cc14f5..2bcb577d9d 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java @@ -26,6 +26,11 @@ public boolean isDefinitelyClosed(DexClass clazz) { return !openInterfaceTypes.contains(clazz.getType()); } + @Override + public boolean isEmpty() { + return openInterfaceTypes.isEmpty(); + } + @Override public OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens, Timing timing) { return timing.time( diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java index de5075ecd6..6c81c5041d 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java @@ -33,6 +33,8 @@ public static DefaultOpenClosedInterfacesCollection getDefault() { public abstract boolean isDefinitelyClosed(DexClass clazz); + public abstract boolean isEmpty(); + public final boolean isMaybeOpen(DexClass clazz) { return !isDefinitelyClosed(clazz); } diff --git a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java index d9aba6c0cd..c45b7e34a0 100644 --- a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java @@ -46,6 +46,10 @@ public AssumeInfo get(DexClassAndMember member) { return get(member.getReference()); } + public boolean isEmpty() { + return backing.isEmpty(); + } + public boolean isMaterializableInAllContexts( AppView appView, DexClassAndMember member) { AbstractValue assumeValue = get(member).getAssumeValue(); diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java index 04098a23eb..373b1e1015 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java @@ -28,6 +28,10 @@ public boolean isCompatInstantiated(DexProgramClass clazz) { return compatInstantiatedTypes.contains(clazz.getType()); } + public boolean isEmpty() { + return compatInstantiatedTypes.isEmpty(); + } + public ProguardCompatibilityActions withoutPrunedItems(PrunedItems prunedItems) { Builder builder = builder(); for (DexType compatInstantiatedType : compatInstantiatedTypes) { diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java index d90c8e552e..1f743e164e 100644 --- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java @@ -50,6 +50,11 @@ public static T[] copyWithSparseChanges( return results; } + public static T[] filled(T[] array, T element) { + Arrays.fill(array, element); + return array; + } + public static T[] initialize(T[] array, IntFunction fn) { for (int i = 0; i < array.length; i++) { array[i] = fn.apply(i); diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java index 528ba57d9a..0e5d90ca48 100644 --- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java @@ -63,6 +63,11 @@ public static Future processAsynchronously( executorService); } + public static void processAsynchronously( + Action action, ExecutorService executorService, Collection> futures) { + futures.add(processAsynchronously(action, executorService)); + } + public static Future processAsynchronously( Callable callable, ExecutorService executorService) { return executorService.submit(callable); diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java index 143bc2fc74..93524d1081 100644 --- a/src/main/java/com/android/tools/r8/utils/Timing.java +++ b/src/main/java/com/android/tools/r8/utils/Timing.java @@ -39,6 +39,11 @@ public void add(Collection timings) { public void end() { // Ignore. } + + @Override + public boolean isEmpty() { + return true; + } }; } @@ -313,7 +318,7 @@ public void report(int depth, Node top) { // merge. children.forEach((title, node) -> node.report(depth + 1, this)); // Print the slowest entry if one was found. - if (slowest.duration > 0) { + if (slowest != null && slowest.duration > 0) { printPrefix(depth); System.out.println("SLOWEST " + slowest.toString(this)); slowest.children.forEach((title, node) -> node.report(depth + 1, this)); @@ -327,6 +332,15 @@ public String toString() { }; } + public TimingMerger disableSlowestReporting() { + slowest = null; + return this; + } + + public boolean isEmpty() { + return false; + } + private static class Item { final Node mergeTarget; final Node mergeSource; @@ -347,7 +361,7 @@ public void add(Collection timings) { assert timing.stack.isEmpty() : "Expected sub-timing to have completed prior to merge"; ++taskCount; merged.duration += timing.top.duration; - if (timing.top.duration > slowest.duration) { + if (slowest != null && timing.top.duration > slowest.duration) { slowest = timing.top; } worklist.addLast(new Item(merged, timing.top)); diff --git a/src/main/java/com/android/tools/r8/utils/threads/FutureBox.java b/src/main/java/com/android/tools/r8/utils/threads/FutureBox.java new file mode 100644 index 0000000000..c69c7fa015 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/threads/FutureBox.java @@ -0,0 +1,43 @@ +// Copyright (c) 2023, 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.utils.threads; + +import com.android.tools.r8.errors.Unimplemented; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class FutureBox implements Future { + + private final T value; + + public FutureBox(T value) { + this.value = value; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + throw new Unimplemented(); + } + + @Override + public boolean isCancelled() { + throw new Unimplemented(); + } + + @Override + public boolean isDone() { + throw new Unimplemented(); + } + + @Override + public T get() { + return value; + } + + @Override + public T get(long timeout, TimeUnit unit) { + return value; + } +} diff --git a/src/main/java/com/android/tools/r8/utils/threads/ThreadTask.java b/src/main/java/com/android/tools/r8/utils/threads/ThreadTask.java new file mode 100644 index 0000000000..84afbdc024 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/threads/ThreadTask.java @@ -0,0 +1,28 @@ +// Copyright (c) 2023, 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.utils.threads; + +import com.android.tools.r8.utils.Timing; + +public interface ThreadTask { + + void run(Timing timing) throws Exception; + + default void runWithRuntimeException(Timing timing) { + try { + run(timing); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + default boolean shouldRun() { + return true; + } + + default boolean shouldRunOnThread() { + return true; + } +} diff --git a/src/main/java/com/android/tools/r8/utils/threads/ThreadTaskUtils.java b/src/main/java/com/android/tools/r8/utils/threads/ThreadTaskUtils.java new file mode 100644 index 0000000000..63733bca2f --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/threads/ThreadTaskUtils.java @@ -0,0 +1,85 @@ +// Copyright (c) 2023, 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.utils.threads; + +import com.android.tools.r8.utils.ArrayUtils; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; +import com.android.tools.r8.utils.Timing.TimingMerger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +public class ThreadTaskUtils { + + public static void processTasks( + ExecutorService executorService, + InternalOptions options, + TimingMerger timingMerger, + ThreadTask... tasks) + throws ExecutionException { + assert tasks.length > 0; + List> futures = new ArrayList<>(tasks.length); + if (timingMerger.isEmpty()) { + for (ThreadTask task : tasks) { + if (task.shouldRun()) { + processTask(executorService, task, futures); + } + } + ThreadUtils.awaitFutures(futures); + } else { + List timings = + Arrays.asList(ArrayUtils.filled(new Timing[tasks.length], Timing.empty())); + int taskIndex = 0; + for (ThreadTask task : tasks) { + if (task.shouldRun()) { + processTaskWithTiming(executorService, options, task, taskIndex++, futures, timings); + } + } + ThreadUtils.awaitFutures(futures); + timingMerger.add(timings); + timingMerger.end(); + } + } + + private static void processTask( + ExecutorService executorService, ThreadTask task, List> futures) { + if (task.shouldRunOnThread()) { + ThreadUtils.processAsynchronously( + () -> task.runWithRuntimeException(Timing.empty()), executorService, futures); + } else { + task.runWithRuntimeException(Timing.empty()); + } + } + + private static void processTaskWithTiming( + ExecutorService executorService, + InternalOptions options, + ThreadTask task, + int taskIndex, + List> futures, + List timings) { + if (task.shouldRunOnThread()) { + ThreadUtils.processAsynchronously( + () -> executeTask(options, task, taskIndex, timings), executorService, futures); + } else { + executeTask(options, task, taskIndex, timings); + } + } + + private static void executeTask( + InternalOptions options, ThreadTask task, int taskIndex, List timings) { + Timing threadTiming = Timing.create("Timing", options); + timings.set(taskIndex, threadTiming); + threadTiming.begin("Task " + (taskIndex + 1)); + task.runWithRuntimeException(threadTiming); + threadTiming.end(); + threadTiming.end(); + } +} From 092cc7652d49e17b67174f0de675b7b9d2b8afaf Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 13:00:16 +0200 Subject: [PATCH 059/153] Prune dead enum lite maps from AppView Change-Id: Ib02e7d374262cec4209c85cfb603007650f640b2 --- src/main/java/com/android/tools/r8/R8.java | 9 ++++++--- .../analysis/proto/EnumLiteProtoShrinker.java | 10 +++++++--- .../android/tools/r8/shaking/TreePruner.java | 18 +++++++----------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index a64aa8e806..88c2ee35fb 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -401,12 +401,13 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO .run(appView.appInfo().classes()); // TODO(b/226539525): Implement enum lite proto shrinking as deferred tracing. + PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder(); if (appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled()) { - appView.protoShrinker().enumLiteProtoShrinker.clearDeadEnumLiteMaps(); + appView.protoShrinker().enumLiteProtoShrinker.clearDeadEnumLiteMaps(prunedItemsBuilder); } TreePruner pruner = new TreePruner(appViewWithLiveness); - PrunedItems prunedItems = pruner.run(executorService, timing); + PrunedItems prunedItems = pruner.run(executorService, timing, prunedItemsBuilder); // Recompute the subtyping information. appView.pruneItems(prunedItems, executorService); @@ -576,7 +577,9 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO GenericSignatureContextBuilder.create(appView); TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration); - PrunedItems prunedItems = pruner.run(executorService, timing, prunedTypes); + PrunedItems prunedItems = + pruner.run( + executorService, timing, PrunedItems.builder().addRemovedClasses(prunedTypes)); if (options.usageInformationConsumer != null) { ExceptionUtils.withFinishedResourceHandler( diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java index 75e46311bf..48c80840d5 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java @@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.google.common.collect.Sets; import java.util.Set; @@ -55,7 +56,7 @@ private DexField createInternalValueMapField(DexType holder) { .createField(holder, references.enumLiteMapType, references.internalValueMapFieldName); } - public void clearDeadEnumLiteMaps() { + public void clearDeadEnumLiteMaps(PrunedItems.Builder prunedItemsBuilder) { assert appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled(); // The optimization only enables further enums to be unboxed, no point to run it if enum // unboxing is disabled. @@ -67,14 +68,17 @@ public void clearDeadEnumLiteMaps() { if (!appView.options().isShrinking()) { return; } - internalClearDeadEnumLiteMaps(); + internalClearDeadEnumLiteMaps(prunedItemsBuilder); } - private void internalClearDeadEnumLiteMaps() { + private void internalClearDeadEnumLiteMaps(PrunedItems.Builder prunedItemsBuilder) { for (DexProgramClass clazz : appView.appInfo().classes()) { if (isDeadEnumLiteMap(clazz)) { deadEnumLiteMaps.add(clazz.getType()); // Clears the EnumLiteMap methods to avoid them being IR processed. + clazz + .virtualMethods() + .forEach(method -> prunedItemsBuilder.addRemovedMethod(method.getReference())); clazz.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY); } } diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java index 6124e823f7..d8c17ba75a 100644 --- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java +++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java @@ -30,7 +30,6 @@ import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.utils.ArrayUtils; -import com.android.tools.r8.utils.CollectionUtils; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.IterableUtils; @@ -39,7 +38,6 @@ import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -75,17 +73,15 @@ public TreePruner(AppView appView, TreePrunerConfiguration : UnusedItemsPrinter.DONT_PRINT; } - public PrunedItems run(ExecutorService executorService, Timing timing) throws ExecutionException { - return run(executorService, timing, Collections.emptySet()); - } - - public PrunedItems run(ExecutorService executorService, Timing timing, Set prunedTypes) + public PrunedItems run( + ExecutorService executorService, Timing timing, PrunedItems.Builder prunedItemsBuilder) throws ExecutionException { - return timing.time("Pruning application...", () -> internalRun(executorService, prunedTypes)); + return timing.time( + "Pruning application...", () -> internalRun(executorService, prunedItemsBuilder)); } private PrunedItems internalRun( - ExecutorService executorService, Set previouslyPrunedTypes) + ExecutorService executorService, PrunedItems.Builder prunedItemsBuilder) throws ExecutionException { DirectMappedDexApplication application = appView.appInfo().app().asDirect(); DirectMappedDexApplication.Builder builder = removeUnused(application); @@ -94,9 +90,9 @@ private PrunedItems internalRun( ? application : builder.build(); fixupOptimizationInfo(newApplication, executorService); - return PrunedItems.builder() + return prunedItemsBuilder .setPrunedApp(newApplication) - .addRemovedClasses(CollectionUtils.mergeSets(previouslyPrunedTypes, prunedTypes)) + .addRemovedClasses(prunedTypes) .addRemovedFields(prunedFields) .addRemovedMethods(prunedMethods) .addAdditionalPinnedItems(methodsToKeepForConfigurationDebugging) From 4d276fcd4afe04146ef1965c488c3860994b8c50 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 13:00:26 +0200 Subject: [PATCH 060/153] Support for creating ProgramMethodSet with initial capacity Change-Id: I262414fbe8cbbbdf01e086223031e04e37610174 --- .../collections/DexClassAndMethodSet.java | 88 ++++++++++++------- .../collections/DexClassAndMethodSetBase.java | 28 ++++-- .../collections/LinkedProgramMethodSet.java | 13 ++- .../utils/collections/ProgramMethodSet.java | 80 +++++++++++++---- .../collections/SortedProgramMethodSet.java | 54 ++++++++---- 5 files changed, 191 insertions(+), 72 deletions(-) diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSet.java index ad7c3fed8f..3832ccecab 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSet.java @@ -11,56 +11,84 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; -public class DexClassAndMethodSet extends DexClassAndMethodSetBase { +public abstract class DexClassAndMethodSet extends DexClassAndMethodSetBase { - private static final DexClassAndMethodSet EMPTY = new DexClassAndMethodSet(ImmutableMap::of); + private static final DexClassAndMethodSet EMPTY = new EmptyDexClassAndMethodSet(); - protected DexClassAndMethodSet( - Supplier> backingFactory) { - super(backingFactory); + DexClassAndMethodSet() { + super(); } - protected DexClassAndMethodSet( - Supplier> backingFactory, - Map backing) { - super(backingFactory, backing); + public static DexClassAndMethodSet create() { + return new IdentityDexClassAndMethodSet(); } - public static DexClassAndMethodSet create() { - return new DexClassAndMethodSet(IdentityHashMap::new); + public static DexClassAndMethodSet createConcurrent() { + return new ConcurrentDexClassAndMethodSet(); } - public static DexClassAndMethodSet create(int capacity) { - return new DexClassAndMethodSet(IdentityHashMap::new, new IdentityHashMap<>(capacity)); + public static DexClassAndMethodSet createLinked() { + return new LinkedDexClassAndMethodSet(); } - public static DexClassAndMethodSet create(DexClassAndMethod element) { - DexClassAndMethodSet result = create(); - result.add(element); - return result; + public static DexClassAndMethodSet empty() { + return EMPTY; } - public static DexClassAndMethodSet create(DexClassAndMethodSet methodSet) { - DexClassAndMethodSet newMethodSet = create(); - newMethodSet.addAll(methodSet); - return newMethodSet; + public void addAll(DexClassAndMethodSet methods) { + backing.putAll(methods.backing); } - public static DexClassAndMethodSet createConcurrent() { - return new DexClassAndMethodSet(ConcurrentHashMap::new); + private static class ConcurrentDexClassAndMethodSet extends DexClassAndMethodSet { + + @Override + Map createBacking() { + return new ConcurrentHashMap<>(); + } + + @Override + Map createBacking(int capacity) { + return new ConcurrentHashMap<>(capacity); + } } - public static DexClassAndMethodSet createLinked() { - return new DexClassAndMethodSet(LinkedHashMap::new); + private static class EmptyDexClassAndMethodSet extends DexClassAndMethodSet { + + @Override + Map createBacking() { + return ImmutableMap.of(); + } + + @Override + Map createBacking(int capacity) { + return ImmutableMap.of(); + } } - public static DexClassAndMethodSet empty() { - return EMPTY; + private static class IdentityDexClassAndMethodSet extends DexClassAndMethodSet { + + @Override + Map createBacking() { + return new IdentityHashMap<>(); + } + + @Override + Map createBacking(int capacity) { + return new IdentityHashMap<>(capacity); + } } - public void addAll(DexClassAndMethodSet methods) { - backing.putAll(methods.backing); + private static class LinkedDexClassAndMethodSet extends DexClassAndMethodSet { + + @Override + Map createBacking() { + return new LinkedHashMap<>(); + } + + @Override + Map createBacking(int capacity) { + return new LinkedHashMap<>(capacity); + } } } diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java index 0787662499..007aa51346 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java +++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java @@ -16,25 +16,29 @@ import java.util.Set; import java.util.function.IntFunction; import java.util.function.Predicate; -import java.util.function.Supplier; import java.util.stream.Stream; public abstract class DexClassAndMethodSetBase implements Collection { - protected final Map backing; - protected final Supplier> backingFactory; + Map backing; - protected DexClassAndMethodSetBase(Supplier> backingFactory) { - this(backingFactory, backingFactory.get()); + DexClassAndMethodSetBase() { + this.backing = createBacking(); } - protected DexClassAndMethodSetBase( - Supplier> backingFactory, Map backing) { + DexClassAndMethodSetBase(Map backing) { this.backing = backing; - this.backingFactory = backingFactory; } + DexClassAndMethodSetBase(int capacity) { + this.backing = createBacking(capacity); + } + + abstract Map createBacking(); + + abstract Map createBacking(int capacity); + @Override public boolean add(T method) { T existing = backing.put(method.getReference(), method); @@ -171,4 +175,12 @@ public Set toDefinitionSet(IntFunction> forEach(method -> definitions.add(method.getDefinition())); return definitions; } + + public void trimCapacityIfSizeLessThan(int expectedSize) { + if (size() < expectedSize) { + Map newBacking = createBacking(size()); + newBacking.putAll(backing); + backing = newBacking; + } + } } diff --git a/src/main/java/com/android/tools/r8/utils/collections/LinkedProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/LinkedProgramMethodSet.java index b323e7e329..258ee4f1e7 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/LinkedProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/LinkedProgramMethodSet.java @@ -4,23 +4,28 @@ package com.android.tools.r8.utils.collections; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.ProgramMethod; import java.util.LinkedHashMap; +import java.util.Map; public class LinkedProgramMethodSet extends ProgramMethodSet { LinkedProgramMethodSet() { - super(LinkedProgramMethodSet::createBacking, createBacking()); + super(); } LinkedProgramMethodSet(int capacity) { - super(LinkedProgramMethodSet::createBacking, createBacking(capacity)); + super(capacity); } - private static LinkedHashMap createBacking() { + @Override + Map createBacking() { return new LinkedHashMap<>(); } - private static LinkedHashMap createBacking(int capacity) { + @Override + Map createBacking(int capacity) { return new LinkedHashMap<>(capacity); } } diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java index 57ad688b5a..b2ece0b6a3 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java @@ -15,32 +15,33 @@ import java.util.IdentityHashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; -public class ProgramMethodSet extends DexClassAndMethodSetBase { +public abstract class ProgramMethodSet extends DexClassAndMethodSetBase { - private static final ProgramMethodSet EMPTY = new ProgramMethodSet(ImmutableMap::of); + private static final ProgramMethodSet EMPTY = new EmptyProgramMethodSet(); - protected ProgramMethodSet(Supplier> backingFactory) { - super(backingFactory); + ProgramMethodSet() { + super(); } - protected ProgramMethodSet( - Supplier> backingFactory, - Map backing) { - super(backingFactory, backing); + ProgramMethodSet(Map backing) { + super(backing); + } + + ProgramMethodSet(int capacity) { + super(capacity); } public static ProgramMethodSet create() { - return new ProgramMethodSet(IdentityHashMap::new); + return new IdentityProgramMethodSet(); } public static ProgramMethodSet create(int capacity) { - return new ProgramMethodSet(IdentityHashMap::new, new IdentityHashMap<>(capacity)); + return new IdentityProgramMethodSet(capacity); } public static ProgramMethodSet create(ProgramMethod element) { - ProgramMethodSet result = create(); + ProgramMethodSet result = create(1); result.add(element); return result; } @@ -52,13 +53,13 @@ public static ProgramMethodSet create(ForEachable methods) { } public static ProgramMethodSet create(ProgramMethodSet methodSet) { - ProgramMethodSet newMethodSet = create(); + ProgramMethodSet newMethodSet = create(methodSet.size()); newMethodSet.addAll(methodSet); return newMethodSet; } public static ProgramMethodSet createConcurrent() { - return new ProgramMethodSet(ConcurrentHashMap::new); + return new ConcurrentProgramMethodSet(); } public static LinkedProgramMethodSet createLinked() { @@ -82,14 +83,63 @@ public boolean createAndAdd(DexProgramClass clazz, DexEncodedMethod definition) } public ProgramMethodSet rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) { - ProgramMethodSet rewritten = new ProgramMethodSet(backingFactory); + ProgramMethodSet rewritten = ProgramMethodSet.create(size()); forEach( method -> { ProgramMethod newMethod = lens.mapProgramMethod(method, definitions); if (newMethod != null) { + assert !newMethod.getDefinition().isObsolete(); rewritten.add(newMethod); } }); + rewritten.trimCapacityIfSizeLessThan(size()); return rewritten; } + + private static class ConcurrentProgramMethodSet extends ProgramMethodSet { + + @Override + Map createBacking() { + return new ConcurrentHashMap<>(); + } + + @Override + Map createBacking(int capacity) { + return new ConcurrentHashMap<>(capacity); + } + } + + private static class EmptyProgramMethodSet extends ProgramMethodSet { + + @Override + Map createBacking() { + return ImmutableMap.of(); + } + + @Override + Map createBacking(int capacity) { + return ImmutableMap.of(); + } + } + + private static class IdentityProgramMethodSet extends ProgramMethodSet { + + IdentityProgramMethodSet() { + super(); + } + + IdentityProgramMethodSet(int capacity) { + super(capacity); + } + + @Override + Map createBacking() { + return new IdentityHashMap<>(); + } + + @Override + Map createBacking(int capacity) { + return new IdentityHashMap<>(capacity); + } + } } diff --git a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java index 3a17dc5287..aa24dde684 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java @@ -9,28 +9,25 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.lens.GraphLens; -import com.android.tools.r8.utils.ComparatorUtils; import com.android.tools.r8.utils.ForEachable; -import com.android.tools.r8.utils.ForEachableUtils; +import java.util.Collections; import java.util.Comparator; +import java.util.Map; import java.util.Set; -import java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentSkipListMap; -import java.util.function.Supplier; -public class SortedProgramMethodSet extends ProgramMethodSet { +public abstract class SortedProgramMethodSet extends ProgramMethodSet { - private static final SortedProgramMethodSet EMPTY = - new SortedProgramMethodSet(() -> new TreeMap<>(ComparatorUtils.unreachableComparator())); + private static final SortedProgramMethodSet EMPTY = new EmptySortedProgramMethodSet(); - private SortedProgramMethodSet(Supplier> backingFactory) { - super(backingFactory); + private SortedProgramMethodSet() { + super(); } public static SortedProgramMethodSet create() { - return create(ForEachableUtils.empty()); + return new TreeSortedProgramMethodSet(); } public static SortedProgramMethodSet create(ProgramMethod method) { @@ -40,20 +37,24 @@ public static SortedProgramMethodSet create(ProgramMethod method) { } public static SortedProgramMethodSet create(ForEachable methods) { - SortedProgramMethodSet result = - new SortedProgramMethodSet(() -> new TreeMap<>(DexMethod::compareTo)); + SortedProgramMethodSet result = create(); methods.forEach(result::add); return result; } public static SortedProgramMethodSet createConcurrent() { - return new SortedProgramMethodSet(() -> new ConcurrentSkipListMap<>(DexMethod::compareTo)); + return new ConcurrentSortedProgramMethodSet(); } public static SortedProgramMethodSet empty() { return EMPTY; } + @Override + Map createBacking(int capacity) { + return createBacking(); + } + @Override public SortedProgramMethodSet rewrittenWithLens( DexDefinitionSupplier definitions, GraphLens lens) { @@ -63,10 +64,33 @@ public SortedProgramMethodSet rewrittenWithLens( @Override public Set toDefinitionSet() { - Comparator comparator = - (x, y) -> x.getReference().compareTo(y.getReference()); + Comparator comparator = Comparator.comparing(DexEncodedMethod::getReference); Set definitions = new TreeSet<>(comparator); forEach(method -> definitions.add(method.getDefinition())); return definitions; } + + private static class ConcurrentSortedProgramMethodSet extends SortedProgramMethodSet { + + @Override + Map createBacking() { + return new ConcurrentSkipListMap<>(DexMethod::compareTo); + } + } + + private static class EmptySortedProgramMethodSet extends SortedProgramMethodSet { + + @Override + Map createBacking() { + return Collections.emptyMap(); + } + } + + private static class TreeSortedProgramMethodSet extends SortedProgramMethodSet { + + @Override + Map createBacking() { + return new TreeMap<>(DexMethod::compareTo); + } + } } From 31aa5c792aebca163a8bbf19ce645be1d874ffdd Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 13:00:36 +0200 Subject: [PATCH 061/153] Skip lookup for non-obsolete methods whose signatures have not changed Change-Id: I7674c3261261c526454fc48bfba94d8c4e3a8daf --- .../android/tools/r8/graph/lens/GraphLens.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index bbd068e9d1..80684df369 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java @@ -3,12 +3,13 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph.lens; -import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; +import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexCallSite; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; @@ -199,8 +200,18 @@ public DexEncodedMethod mapDexEncodedMethod( public ProgramMethod mapProgramMethod( ProgramMethod oldMethod, DexDefinitionSupplier definitions) { DexMethod newMethod = getRenamedMethodSignature(oldMethod.getReference()); - DexProgramClass holder = asProgramClassOrNull(definitions.definitionForHolder(newMethod)); - return newMethod.lookupOnProgramClass(holder); + if (newMethod == oldMethod.getReference() && !oldMethod.getDefinition().isObsolete()) { + assert verifyIsConsistentWithLookup(oldMethod, definitions); + return oldMethod; + } + return asProgramMethodOrNull(definitions.definitionFor(newMethod)); + } + + private static boolean verifyIsConsistentWithLookup( + ProgramMethod method, DexDefinitionSupplier definitions) { + DexClassAndMethod lookupMethod = definitions.definitionFor(method.getReference()); + assert method.getDefinition() == lookupMethod.getDefinition(); + return true; } // Predicate indicating if a rewritten reference is a simple renaming, meaning the move from one From 0252f5d2269bda272e573837da5b12fba493dc84 Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Fri, 9 Jun 2023 13:07:43 +0200 Subject: [PATCH 062/153] Add a check release column to check for missing cherry-picks Bug: b/286177952 Change-Id: I9d9447e68e5e6064666496533141528d339cad3e --- .../global/generated/cr-buildbucket.cfg | 29 +++++++++++++++++++ infra/config/global/generated/luci-milo.cfg | 5 ++++ infra/config/global/generated/luci-notify.cfg | 12 ++++++++ .../global/generated/luci-scheduler.cfg | 16 ++++++++++ infra/config/global/generated/project.cfg | 2 +- infra/config/global/main.star | 18 ++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg index aa285e07e0..7c515d5251 100644 --- a/infra/config/global/generated/cr-buildbucket.cfg +++ b/infra/config/global/generated/cr-buildbucket.cfg @@ -76,6 +76,35 @@ buckets { value: 100 } } + builders { + name: "check_release" + swarming_host: "chrome-swarming.appspot.com" + swarming_tags: "vpython:native-python-wrapper" + dimensions: "cores:8" + dimensions: "cpu:x86-64" + dimensions: "os:Ubuntu-20.04" + dimensions: "pool:luci.r8.ci" + exe { + cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" + cipd_version: "refs/heads/master" + cmd: "luciexe" + } + properties: + '{' + ' "builder_group": "internal.client.r8",' + ' "recipe": "rex",' + ' "test_wrapper": "tools/check-cherry-picks.py"' + '}' + priority: 25 + execution_timeout_secs: 1800 + expiration_secs: 126000 + build_numbers: YES + service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + } builders { name: "desugared_library-head" swarming_host: "chrome-swarming.appspot.com" diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg index b1481fe595..6020a0605b 100644 --- a/infra/config/global/generated/luci-milo.cfg +++ b/infra/config/global/generated/luci-milo.cfg @@ -155,6 +155,11 @@ consoles { category: "Release|archive" short_name: "archive" } + builders { + name: "buildbucket/luci.r8.ci/check_release" + category: "Release|archive" + short_name: "check" + } builders { name: "buildbucket/luci.r8.ci/linux-dex_default_release" category: "Release|R8" diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg index fa8d5b0aab..406f9e48e6 100644 --- a/infra/config/global/generated/luci-notify.cfg +++ b/infra/config/global/generated/luci-notify.cfg @@ -28,6 +28,18 @@ notifiers { repository: "https://r8.googlesource.com/r8" } } +notifiers { + notifications { + on_failure: true + on_new_failure: true + notify_blamelist {} + } + builders { + bucket: "ci" + name: "check_release" + repository: "https://r8.googlesource.com/r8" + } +} notifiers { notifications { on_failure: true diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg index e59d20c1e8..cc010ab644 100644 --- a/infra/config/global/generated/luci-scheduler.cfg +++ b/infra/config/global/generated/luci-scheduler.cfg @@ -34,6 +34,21 @@ job { builder: "archive_release" } } +job { + id: "check_release" + realm: "ci" + acl_sets: "ci" + triggering_policy { + kind: GREEDY_BATCHING + max_concurrent_invocations: 3 + max_batch_size: 1 + } + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "ci" + builder: "check_release" + } +} job { id: "desugared_library-head" realm: "ci" @@ -770,6 +785,7 @@ trigger { realm: "ci" acl_sets: "ci" triggers: "archive_release" + triggers: "check_release" triggers: "linux-android-10.0.0_release" triggers: "linux-android-4.0.4_release" triggers: "linux-android-4.4.4_release" diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg index cfee79c7e6..ee2ef9d548 100644 --- a/infra/config/global/generated/project.cfg +++ b/infra/config/global/generated/project.cfg @@ -7,7 +7,7 @@ name: "r8" access: "group:all" lucicfg { - version: "1.39.4" + version: "1.39.8" package_dir: ".." config_dir: "generated" entry_point: "main.star" diff --git a/infra/config/global/main.star b/infra/config/global/main.star index d9201a8de5..74c5548e61 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star @@ -283,6 +283,24 @@ def archivers(): ) archivers() +r8_builder( + "check_release", + category = "archive", + dimensions = get_dimensions(), + triggering_policy = scheduler.policy( + kind = scheduler.GREEDY_BATCHING_KIND, + max_batch_size = 1, + max_concurrent_invocations = 3 + ), + priority = 25, + properties = { + "test_wrapper" : "tools/check-cherry-picks.py", + "builder_group" : "internal.client.r8" + }, + execution_timeout = time.minute * 30, + expiration_timeout = time.hour * 35, +) + r8_tester_with_default("linux-dex_default", ["--runtimes=dex-default"]) r8_tester_with_default("linux-none", ["--runtimes=none"]) r8_tester_with_default("linux-jdk8", ["--runtimes=jdk8"]) From ce40d7f2d09305cc816ec15f9055c7130d39aba3 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 13:01:35 +0200 Subject: [PATCH 063/153] Implement pruning of MethodAccessInfoCollection Change-Id: Ia47e61de3634943544431a2432dd97a914064adb --- src/main/java/com/android/tools/r8/R8.java | 3 + .../r8/graph/AppInfoWithClassHierarchy.java | 10 +++ .../r8/graph/MethodAccessInfoCollection.java | 69 +++++++++++++++++-- .../tools/r8/shaking/AppInfoWithLiveness.java | 16 ++++- 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 88c2ee35fb..91729cf4ea 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -495,6 +495,7 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness) .runIfNecessary(executorService, timing, runtimeTypeCheckInfo); } + appView.appInfo().notifyHorizontalClassMergerFinished(HorizontalClassMerger.Mode.INITIAL); new ProtoNormalizer(appViewWithLiveness).run(executorService, timing); @@ -723,6 +724,7 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO classMergingEnqueuerExtensionBuilder != null ? classMergingEnqueuerExtensionBuilder.build(appView.graphLens()) : null); + appView.appInfo().notifyHorizontalClassMergerFinished(HorizontalClassMerger.Mode.FINAL); // Perform minification. if (options.getProguardConfiguration().hasApplyMappingFile()) { @@ -737,6 +739,7 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO appView.setNamingLens(new Minifier(appView.withLiveness()).run(executorService, timing)); timing.end(); } + appView.appInfo().notifyMinifierFinished(); if (!options.isMinifying() && appView.options().testing.enableRecordModeling diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java index e83415393f..3040ebe557 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java @@ -8,6 +8,7 @@ import static com.android.tools.r8.utils.TraversalContinuation.doContinue; import com.android.tools.r8.features.ClassToFeatureSplitMap; +import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger; import com.android.tools.r8.ir.analysis.type.InterfaceCollection; import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder; import com.android.tools.r8.ir.desugar.LambdaDescriptor; @@ -93,6 +94,15 @@ public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(CommittedItems commit, getClassToFeatureSplitMap(), getMainDexInfo(), getMissingClasses()); } + public void notifyHorizontalClassMergerFinished( + HorizontalClassMerger.Mode horizontalClassMergerMode) { + // Intentionally empty. + } + + public void notifyMinifierFinished() { + // Intentionally empty. + } + public AppInfoWithClassHierarchy rebuildWithClassHierarchy( Function fn) { assert checkIfObsolete(); diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java index f1fb1b990a..44f13acdd5 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java +++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java @@ -27,6 +27,8 @@ public class MethodAccessInfoCollection { private final Map superInvokes; private final Map virtualInvokes; + private boolean destroyed = false; + private MethodAccessInfoCollection( Map directInvokes, Map interfaceInvokes, @@ -48,12 +50,24 @@ public static IdentityBuilder identityBuilder() { return new IdentityBuilder(); } + public void destroy() { + assert !destroyed; + directInvokes.clear(); + interfaceInvokes.clear(); + staticInvokes.clear(); + superInvokes.clear(); + virtualInvokes.clear(); + destroyed = true; + } + public Modifier modifier() { + assert !destroyed; return new Modifier( directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes); } public void forEachMethodReference(Consumer method) { + assert !destroyed; Set seen = Sets.newIdentityHashSet(); directInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen)); interfaceInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen)); @@ -63,43 +77,55 @@ public void forEachMethodReference(Consumer method) { } public void forEachDirectInvoke(BiConsumer consumer) { + assert !destroyed; directInvokes.forEach(consumer); } public void forEachInterfaceInvoke(BiConsumer consumer) { + assert !destroyed; interfaceInvokes.forEach(consumer); } public void forEachStaticInvoke(BiConsumer consumer) { + assert !destroyed; staticInvokes.forEach(consumer); } public void forEachSuperInvoke(BiConsumer consumer) { + assert !destroyed; superInvokes.forEach(consumer); } public void forEachSuperInvokeContext(DexMethod method, Consumer consumer) { + assert !destroyed; superInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer); } public void forEachVirtualInvoke(BiConsumer consumer) { + assert !destroyed; virtualInvokes.forEach(consumer); } public void forEachVirtualInvokeContext(DexMethod method, Consumer consumer) { + assert !destroyed; virtualInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer); } public MethodAccessInfoCollection rewrittenWithLens( DexDefinitionSupplier definitions, GraphLens lens, Timing timing) { timing.begin("Rewrite MethodAccessInfoCollection"); - MethodAccessInfoCollection.Builder builder = identityBuilder(); - rewriteInvokesWithLens(builder, directInvokes, definitions, lens, InvokeType.DIRECT); - rewriteInvokesWithLens(builder, interfaceInvokes, definitions, lens, InvokeType.INTERFACE); - rewriteInvokesWithLens(builder, staticInvokes, definitions, lens, InvokeType.STATIC); - rewriteInvokesWithLens(builder, superInvokes, definitions, lens, InvokeType.SUPER); - rewriteInvokesWithLens(builder, virtualInvokes, definitions, lens, InvokeType.VIRTUAL); - MethodAccessInfoCollection result = builder.build(); + MethodAccessInfoCollection result; + if (destroyed) { + result = this; + } else { + MethodAccessInfoCollection.Builder builder = identityBuilder(); + rewriteInvokesWithLens(builder, directInvokes, definitions, lens, InvokeType.DIRECT); + rewriteInvokesWithLens(builder, interfaceInvokes, definitions, lens, InvokeType.INTERFACE); + rewriteInvokesWithLens(builder, staticInvokes, definitions, lens, InvokeType.STATIC); + rewriteInvokesWithLens(builder, superInvokes, definitions, lens, InvokeType.SUPER); + rewriteInvokesWithLens(builder, virtualInvokes, definitions, lens, InvokeType.VIRTUAL); + result = builder.build(); + } timing.end(); return result; } @@ -123,6 +149,35 @@ private static void rewriteInvokesWithLens( }); } + public MethodAccessInfoCollection withoutPrunedItems(PrunedItems prunedItems) { + if (!destroyed) { + pruneItems(prunedItems, directInvokes); + pruneItems(prunedItems, interfaceInvokes); + pruneItems(prunedItems, staticInvokes); + pruneItems(prunedItems, superInvokes); + pruneItems(prunedItems, virtualInvokes); + } + return this; + } + + private static void pruneItems( + PrunedItems prunedItems, Map invokes) { + invokes + .values() + .removeIf( + contexts -> { + contexts.removeIf( + context -> { + if (prunedItems.isRemoved(context.getReference())) { + return true; + } + assert prunedItems.getPrunedApp().definitionFor(context.getReference()) != null; + return false; + }); + return contexts.isEmpty(); + }); + } + public abstract static class Builder> { private final T directInvokes; diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java index a3ef43f54f..a7e9d85254 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java @@ -48,6 +48,7 @@ import com.android.tools.r8.graph.SubtypingInfo; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.graph.lens.NonIdentityGraphLens; +import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.DynamicType; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; @@ -328,7 +329,7 @@ private AppInfoWithLiveness( previous.virtualMethodsTargetedByInvokeDirect, prunedItems, executorService, futures), pruneMethods(previous.liveMethods, prunedItems, executorService, futures), previous.fieldAccessInfoCollection, - previous.methodAccessInfoCollection, + previous.methodAccessInfoCollection.withoutPrunedItems(prunedItems), previous.objectAllocationInfoCollection.withoutPrunedItems(prunedItems), previous.callSites, extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()), @@ -468,6 +469,19 @@ private static Map pruneMap( return map; } + @Override + public void notifyHorizontalClassMergerFinished( + HorizontalClassMerger.Mode horizontalClassMergerMode) { + if (horizontalClassMergerMode.isInitial()) { + methodAccessInfoCollection.destroy(); + } + } + + @Override + public void notifyMinifierFinished() { + methodAccessInfoCollection.destroy(); + } + private boolean verify() { assert keepInfo.verifyPinnedTypesAreLive(liveTypes, options()); assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive( From 2277787a9aad2515b301cb86a3934a934df6445a Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 14:06:25 +0200 Subject: [PATCH 064/153] Fix inadequate pruning of AppInfoWithLiveness Change-Id: Iabae750c91b6477bf491eba16738c8ed256d8342 --- .../r8/horizontalclassmerging/TreeFixer.java | 4 +++- .../tools/r8/shaking/AppInfoWithLiveness.java | 19 ++++++++++++++++++- .../utils/collections/ProgramMethodSet.java | 6 ++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java index 482b43f8b4..804b505dfa 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java @@ -232,7 +232,9 @@ private DexEncodedMethod fixupVirtualInterfaceMethod(DexEncodedMethod method) { DexMethod newMethodReference = newMethodSignature.withHolder(originalMethodReference, dexItemFactory); lensBuilder.fixupMethod(originalMethodReference, newMethodReference); - return method.toTypeSubstitutedMethod(newMethodReference); + return newMethodReference != originalMethodReference + ? method.toTypeSubstitutedMethod(newMethodReference) + : method; } private void fixupInterfaceClass(DexProgramClass iface) { diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java index a7e9d85254..0cb8daeba0 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java @@ -331,7 +331,7 @@ private AppInfoWithLiveness( previous.fieldAccessInfoCollection, previous.methodAccessInfoCollection.withoutPrunedItems(prunedItems), previous.objectAllocationInfoCollection.withoutPrunedItems(prunedItems), - previous.callSites, + pruneCallSites(previous.callSites, prunedItems), extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()), previous.mayHaveSideEffects, pruneMethods(previous.alwaysInline, prunedItems, executorService, futures), @@ -355,6 +355,23 @@ private AppInfoWithLiveness( previous.recordFieldValuesReferences); } + private static Map pruneCallSites( + Map callSites, PrunedItems prunedItems) { + callSites + .entrySet() + .removeIf( + entry -> { + ProgramMethodSet contexts = entry.getValue(); + ProgramMethodSet prunedContexts = contexts.withoutPrunedItems(prunedItems); + if (prunedContexts.isEmpty()) { + return true; + } + entry.setValue(prunedContexts); + return false; + }); + return callSites; + } + private static Set pruneClasses( Set methods, PrunedItems prunedItems, diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java index b2ece0b6a3..612ace4220 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java @@ -9,6 +9,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.utils.ForEachable; import com.google.common.collect.ImmutableMap; @@ -96,6 +97,11 @@ public ProgramMethodSet rewrittenWithLens(DexDefinitionSupplier definitions, Gra return rewritten; } + public ProgramMethodSet withoutPrunedItems(PrunedItems prunedItems) { + removeIf(method -> prunedItems.isRemoved(method.getReference())); + return this; + } + private static class ConcurrentProgramMethodSet extends ProgramMethodSet { @Override From 1721181bf9fea454cccec1caa644a4e6d9c2539a Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 14:16:20 +0200 Subject: [PATCH 065/153] Allow second optimization pass to be non-empty in debug mode Change-Id: I78ba69ec99c6345600391a19347f71dcbaed6e93 --- .../android/tools/r8/ir/conversion/PostMethodProcessor.java | 3 +++ .../android/tools/r8/ir/conversion/PrimaryR8IRConverter.java | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java index ab073ddccb..03f885931e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java @@ -152,6 +152,9 @@ PostMethodProcessor build( return null; } ProgramMethodSet methodsToReprocess = methodsToReprocessBuilder.build(appView); + assert !appView.options().debug + || methodsToReprocess.stream() + .allMatch(methodToReprocess -> methodToReprocess.getDefinition().isD8R8Synthesized()); CallGraph callGraph = new PartialCallGraphBuilder(appView, methodsToReprocess).build(executorService, timing); return new PostMethodProcessor(appView, callGraph, eventConsumer); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java index 2840c77386..f046229a83 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java @@ -162,7 +162,6 @@ private DexApplication internalOptimize( postMethodProcessorBuilder.build( appView, eventConsumer, executorService, timing)); if (postMethodProcessor != null) { - assert !options.debug; assert appView.graphLens() == graphLensForSecondaryOptimizationPass; timing.begin("Process code"); postMethodProcessor.forEachMethod( From 93630a438b7fbbe847973cd7c5e67a4bcb76f431 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 19:38:03 +0200 Subject: [PATCH 066/153] Destroy some AppInfoWithLiveness collections after last use Change-Id: I71e49b7616f03e910026a77f98a050e14bfb3992 --- src/main/java/com/android/tools/r8/R8.java | 12 ++- .../r8/graph/MethodAccessInfoCollection.java | 86 ++++++++++++------ .../tools/r8/graph/lens/GraphLens.java | 4 + .../tools/r8/shaking/AppInfoWithLiveness.java | 22 ++++- .../r8/utils/collections/ThrowingMap.java | 88 ++++++++++++++++++ .../r8/utils/collections/ThrowingSet.java | 91 +++++++++++++++++++ 6 files changed, 268 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/utils/collections/ThrowingMap.java create mode 100644 src/main/java/com/android/tools/r8/utils/collections/ThrowingSet.java diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 91729cf4ea..f345f1b46c 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -408,6 +408,9 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO TreePruner pruner = new TreePruner(appViewWithLiveness); PrunedItems prunedItems = pruner.run(executorService, timing, prunedItemsBuilder); + appViewWithLiveness + .appInfo() + .notifyTreePrunerFinished(Enqueuer.Mode.INITIAL_TREE_SHAKING); // Recompute the subtyping information. appView.pruneItems(prunedItems, executorService); @@ -459,7 +462,7 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO new NestReducer(appViewWithLiveness).run(executorService, timing); new MemberRebindingAnalysis(appViewWithLiveness).run(executorService); - appView.appInfo().withLiveness().getFieldAccessInfoCollection().restrictToProgram(appView); + appViewWithLiveness.appInfo().notifyMemberRebindingFinished(appViewWithLiveness); boolean isKotlinLibraryCompilationWithInlinePassThrough = options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods(); @@ -495,7 +498,9 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness) .runIfNecessary(executorService, timing, runtimeTypeCheckInfo); } - appView.appInfo().notifyHorizontalClassMergerFinished(HorizontalClassMerger.Mode.INITIAL); + appViewWithLiveness + .appInfo() + .notifyHorizontalClassMergerFinished(HorizontalClassMerger.Mode.INITIAL); new ProtoNormalizer(appViewWithLiveness).run(executorService, timing); @@ -581,6 +586,9 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO PrunedItems prunedItems = pruner.run( executorService, timing, PrunedItems.builder().addRemovedClasses(prunedTypes)); + appViewWithLiveness + .appInfo() + .notifyTreePrunerFinished(Enqueuer.Mode.FINAL_TREE_SHAKING); if (options.usageInformationConsumer != null) { ExceptionUtils.withFinishedResourceHandler( diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java index 44f13acdd5..68f9b0a16c 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java +++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java @@ -4,12 +4,15 @@ package com.android.tools.r8.graph; +import static com.android.tools.r8.utils.collections.ThrowingMap.isThrowingMap; + import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.graph.lens.MethodLookupResult; import com.android.tools.r8.ir.code.InvokeType; import com.android.tools.r8.utils.ConsumerUtils; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.ProgramMethodSet; +import com.android.tools.r8.utils.collections.ThrowingMap; import com.google.common.collect.Sets; import java.util.IdentityHashMap; import java.util.Map; @@ -21,13 +24,13 @@ public class MethodAccessInfoCollection { - private final Map directInvokes; - private final Map interfaceInvokes; - private final Map staticInvokes; - private final Map superInvokes; - private final Map virtualInvokes; + private Map directInvokes; + private Map interfaceInvokes; + private Map staticInvokes; + private Map superInvokes; + private Map virtualInvokes; - private boolean destroyed = false; + private boolean fullyDestroyed = false; private MethodAccessInfoCollection( Map directInvokes, @@ -51,23 +54,28 @@ public static IdentityBuilder identityBuilder() { } public void destroy() { - assert !destroyed; - directInvokes.clear(); - interfaceInvokes.clear(); - staticInvokes.clear(); - superInvokes.clear(); - virtualInvokes.clear(); - destroyed = true; + assert !fullyDestroyed; + directInvokes = ThrowingMap.get(); + interfaceInvokes = ThrowingMap.get(); + staticInvokes = ThrowingMap.get(); + superInvokes = ThrowingMap.get(); + virtualInvokes = ThrowingMap.get(); + fullyDestroyed = true; + } + + public void destroyNonDirectInvokes() { + interfaceInvokes = ThrowingMap.get(); + staticInvokes = ThrowingMap.get(); + superInvokes = ThrowingMap.get(); + virtualInvokes = ThrowingMap.get(); } public Modifier modifier() { - assert !destroyed; return new Modifier( directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes); } public void forEachMethodReference(Consumer method) { - assert !destroyed; Set seen = Sets.newIdentityHashSet(); directInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen)); interfaceInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen)); @@ -77,37 +85,30 @@ public void forEachMethodReference(Consumer method) { } public void forEachDirectInvoke(BiConsumer consumer) { - assert !destroyed; directInvokes.forEach(consumer); } public void forEachInterfaceInvoke(BiConsumer consumer) { - assert !destroyed; interfaceInvokes.forEach(consumer); } public void forEachStaticInvoke(BiConsumer consumer) { - assert !destroyed; staticInvokes.forEach(consumer); } public void forEachSuperInvoke(BiConsumer consumer) { - assert !destroyed; superInvokes.forEach(consumer); } public void forEachSuperInvokeContext(DexMethod method, Consumer consumer) { - assert !destroyed; superInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer); } public void forEachVirtualInvoke(BiConsumer consumer) { - assert !destroyed; virtualInvokes.forEach(consumer); } public void forEachVirtualInvokeContext(DexMethod method, Consumer consumer) { - assert !destroyed; virtualInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer); } @@ -115,8 +116,17 @@ public MethodAccessInfoCollection rewrittenWithLens( DexDefinitionSupplier definitions, GraphLens lens, Timing timing) { timing.begin("Rewrite MethodAccessInfoCollection"); MethodAccessInfoCollection result; - if (destroyed) { + if (fullyDestroyed) { result = this; + } else if (isThrowingMap(interfaceInvokes)) { + assert !isThrowingMap(directInvokes); + assert isThrowingMap(staticInvokes); + assert isThrowingMap(superInvokes); + assert isThrowingMap(virtualInvokes); + MethodAccessInfoCollection.Builder builder = identityBuilder(); + rewriteInvokesWithLens(builder, directInvokes, definitions, lens, InvokeType.DIRECT); + result = builder.build(); + result.destroyNonDirectInvokes(); } else { MethodAccessInfoCollection.Builder builder = identityBuilder(); rewriteInvokesWithLens(builder, directInvokes, definitions, lens, InvokeType.DIRECT); @@ -150,7 +160,7 @@ private static void rewriteInvokesWithLens( } public MethodAccessInfoCollection withoutPrunedItems(PrunedItems prunedItems) { - if (!destroyed) { + if (!fullyDestroyed) { pruneItems(prunedItems, directInvokes); pruneItems(prunedItems, interfaceInvokes); pruneItems(prunedItems, staticInvokes); @@ -162,6 +172,9 @@ public MethodAccessInfoCollection withoutPrunedItems(PrunedItems prunedItems) { private static void pruneItems( PrunedItems prunedItems, Map invokes) { + if (isThrowingMap(invokes)) { + return; + } invokes .values() .removeIf( @@ -243,7 +256,7 @@ public boolean registerInvokeDirectInContext(DexMethod invokedMethod, ProgramMet } public void registerInvokeDirectInContexts(DexMethod invokedMethod, ProgramMethodSet contexts) { - contexts.forEach(context -> registerInvokeDirectInContext(invokedMethod, context)); + registerInvokeMethodInContexts(invokedMethod, contexts, directInvokes); } public boolean registerInvokeInterfaceInContext( @@ -253,7 +266,7 @@ public boolean registerInvokeInterfaceInContext( public void registerInvokeInterfaceInContexts( DexMethod invokedMethod, ProgramMethodSet contexts) { - contexts.forEach(context -> registerInvokeInterfaceInContext(invokedMethod, context)); + registerInvokeMethodInContexts(invokedMethod, contexts, interfaceInvokes); } public boolean registerInvokeStaticInContext(DexMethod invokedMethod, ProgramMethod context) { @@ -261,7 +274,7 @@ public boolean registerInvokeStaticInContext(DexMethod invokedMethod, ProgramMet } public void registerInvokeStaticInContexts(DexMethod invokedMethod, ProgramMethodSet contexts) { - contexts.forEach(context -> registerInvokeStaticInContext(invokedMethod, context)); + registerInvokeMethodInContexts(invokedMethod, contexts, staticInvokes); } public boolean registerInvokeSuperInContext(DexMethod invokedMethod, ProgramMethod context) { @@ -269,7 +282,7 @@ public boolean registerInvokeSuperInContext(DexMethod invokedMethod, ProgramMeth } public void registerInvokeSuperInContexts(DexMethod invokedMethod, ProgramMethodSet contexts) { - contexts.forEach(context -> registerInvokeSuperInContext(invokedMethod, context)); + registerInvokeMethodInContexts(invokedMethod, contexts, superInvokes); } public boolean registerInvokeVirtualInContext(DexMethod invokedMethod, ProgramMethod context) { @@ -278,7 +291,7 @@ public boolean registerInvokeVirtualInContext(DexMethod invokedMethod, ProgramMe public void registerInvokeVirtualInContexts( DexMethod invokedMethod, ProgramMethodSet contexts) { - contexts.forEach(context -> registerInvokeVirtualInContext(invokedMethod, context)); + registerInvokeMethodInContexts(invokedMethod, contexts, virtualInvokes); } private static boolean registerInvokeMethodInContext( @@ -288,6 +301,21 @@ private static boolean registerInvokeMethodInContext( .add(context); } + private static void registerInvokeMethodInContexts( + DexMethod invokedMethod, + ProgramMethodSet contexts, + Map invokes) { + ProgramMethodSet existingContexts = invokes.put(invokedMethod, contexts); + if (existingContexts != null) { + if (existingContexts.size() > contexts.size()) { + invokes.put(invokedMethod, existingContexts); + existingContexts.addAll(contexts); + } else { + contexts.addAll(existingContexts); + } + } + } + public MethodAccessInfoCollection build() { return new MethodAccessInfoCollection( directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes); diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index 80684df369..43deea367c 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java @@ -4,6 +4,7 @@ package com.android.tools.r8.graph.lens; import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull; +import static com.android.tools.r8.utils.collections.ThrowingSet.isThrowingSet; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexApplication; @@ -537,6 +538,9 @@ public T rewriteReference(T reference, GraphLens codeLe } public Set rewriteReferences(Set references) { + if (isThrowingSet(references)) { + return references; + } Set result = SetUtils.newIdentityHashSet(references.size()); for (T reference : references) { result.add(rewriteReference(reference)); diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java index 0cb8daeba0..7ba4bb8fa2 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java @@ -7,6 +7,7 @@ import static com.android.tools.r8.graph.DexEncodedMethod.toMethodDefinitionOrNull; import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult.isOverriding; +import static com.android.tools.r8.utils.collections.ThrowingSet.isThrowingSet; import com.android.tools.r8.cf.CfVersion; import com.android.tools.r8.features.ClassToFeatureSplitMap; @@ -70,6 +71,7 @@ import com.android.tools.r8.utils.Visibility; import com.android.tools.r8.utils.WorkList; import com.android.tools.r8.utils.collections.ProgramMethodSet; +import com.android.tools.r8.utils.collections.ThrowingSet; import com.android.tools.r8.utils.structural.Ordered; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -105,7 +107,7 @@ public class AppInfoWithLiveness extends AppInfoWithClassHierarchy * contained in {@link #liveMethods}, it may be marked as abstract and its implementation may be * removed. */ - private final Set targetedMethods; + private Set targetedMethods; /** Method targets that lead to resolution errors such as non-existing or invalid targets. */ private final Set failedMethodResolutionTargets; @@ -124,7 +126,7 @@ public class AppInfoWithLiveness extends AppInfoWithClassHierarchy * Set of methods that belong to live classes and can be reached by invokes. These need to be * kept. */ - private final Set liveMethods; + private Set liveMethods; /** * Information about all fields that are accessed by the program. The information includes whether * a given field is read/written by the program, and it also includes all indirect accesses to @@ -425,8 +427,7 @@ private static Set pruneMethods( private static Set pruneItems( Set items, Set removedItems, ExecutorService executorService, List> futures) { - if (!removedItems.isEmpty()) { - + if (!isThrowingSet(items) && !removedItems.isEmpty()) { futures.add( ThreadUtils.processAsynchronously( () -> { @@ -494,11 +495,24 @@ public void notifyHorizontalClassMergerFinished( } } + public void notifyMemberRebindingFinished(AppView appView) { + getFieldAccessInfoCollection().restrictToProgram(appView); + getMethodAccessInfoCollection().destroyNonDirectInvokes(); + } + @Override public void notifyMinifierFinished() { + liveMethods = ThrowingSet.get(); methodAccessInfoCollection.destroy(); } + public void notifyTreePrunerFinished(Enqueuer.Mode mode) { + if (mode.isInitialTreeShaking()) { + liveMethods = ThrowingSet.get(); + } + targetedMethods = ThrowingSet.get(); + } + private boolean verify() { assert keepInfo.verifyPinnedTypesAreLive(liveTypes, options()); assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive( diff --git a/src/main/java/com/android/tools/r8/utils/collections/ThrowingMap.java b/src/main/java/com/android/tools/r8/utils/collections/ThrowingMap.java new file mode 100644 index 0000000000..bc85834a56 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/collections/ThrowingMap.java @@ -0,0 +1,88 @@ +// Copyright (c) 2023, 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.utils.collections; + +import com.android.tools.r8.errors.Unreachable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import org.jetbrains.annotations.Nullable; + +public class ThrowingMap implements Map { + + private static final ThrowingMap INSTANCE = new ThrowingMap<>(); + + private ThrowingMap() {} + + @SuppressWarnings("unchecked") + public static ThrowingMap get() { + return (ThrowingMap) INSTANCE; + } + + public static boolean isThrowingMap(Map map) { + return map == get(); + } + + @Override + public void clear() { + throw new Unreachable(); + } + + @Override + public boolean containsKey(Object key) { + throw new Unreachable(); + } + + @Override + public boolean containsValue(Object value) { + throw new Unreachable(); + } + + @Override + public Set> entrySet() { + throw new Unreachable(); + } + + @Override + public V get(Object key) { + throw new Unreachable(); + } + + @Override + public boolean isEmpty() { + throw new Unreachable(); + } + + @Override + public Set keySet() { + throw new Unreachable(); + } + + @Nullable + @Override + public V put(K key, V value) { + throw new Unreachable(); + } + + @Override + public void putAll(Map m) { + throw new Unreachable(); + } + + @Override + public V remove(Object key) { + throw new Unreachable(); + } + + @Override + public int size() { + throw new Unreachable(); + } + + @Override + public Collection values() { + throw new Unreachable(); + } +} diff --git a/src/main/java/com/android/tools/r8/utils/collections/ThrowingSet.java b/src/main/java/com/android/tools/r8/utils/collections/ThrowingSet.java new file mode 100644 index 0000000000..5d815ef928 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/collections/ThrowingSet.java @@ -0,0 +1,91 @@ +// Copyright (c) 2023, 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.utils.collections; + +import com.android.tools.r8.errors.Unreachable; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +public class ThrowingSet implements Set { + + private static final ThrowingSet INSTANCE = new ThrowingSet<>(); + + private ThrowingSet() {} + + @SuppressWarnings("unchecked") + public static ThrowingSet get() { + return (ThrowingSet) INSTANCE; + } + + public static boolean isThrowingSet(Set set) { + return set == get(); + } + + @Override + public boolean add(T t) { + throw new Unreachable(); + } + + @Override + public boolean addAll(Collection c) { + throw new Unreachable(); + } + + @Override + public void clear() { + throw new Unreachable(); + } + + @Override + public boolean contains(Object o) { + throw new Unreachable(); + } + + @Override + public boolean containsAll(Collection c) { + throw new Unreachable(); + } + + @Override + public boolean isEmpty() { + throw new Unreachable(); + } + + @Override + public Iterator iterator() { + throw new Unreachable(); + } + + @Override + public boolean remove(Object o) { + throw new Unreachable(); + } + + @Override + public boolean removeAll(Collection c) { + throw new Unreachable(); + } + + @Override + public boolean retainAll(Collection c) { + throw new Unreachable(); + } + + @Override + public int size() { + throw new Unreachable(); + } + + @Override + public Object[] toArray() { + throw new Unreachable(); + } + + @Override + public T1[] toArray(T1[] a) { + throw new Unreachable(); + } +} From 66e67edd1d03a4640218e8185cad9fe67df41b10 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 9 Jun 2023 19:39:36 +0200 Subject: [PATCH 067/153] Avoid ConcurrentHashMap in lens rewriting of minimum keep info Change-Id: I4cb1f71a8ca09d73c3f0faa77d25b5524f74c750 --- .../DependentMinimumKeepInfoCollection.java | 86 +++++++++++++++++-- .../android/tools/r8/shaking/Enqueuer.java | 2 +- .../r8/shaking/MinimumKeepInfoCollection.java | 23 +++-- .../tools/r8/shaking/RootSetUtils.java | 2 +- 4 files changed, 99 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java index 92ba7ffd91..9e9cdc3cf8 100644 --- a/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java @@ -22,19 +22,42 @@ import com.android.tools.r8.utils.MapUtils; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TriConsumer; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.Function; -public class DependentMinimumKeepInfoCollection { +public abstract class DependentMinimumKeepInfoCollection { - private final Map dependentMinimumKeepInfo; + private Map dependentMinimumKeepInfo; - public DependentMinimumKeepInfoCollection() { - this.dependentMinimumKeepInfo = new ConcurrentHashMap<>(); + DependentMinimumKeepInfoCollection() { + this.dependentMinimumKeepInfo = createBacking(); } + DependentMinimumKeepInfoCollection(int capacity) { + this.dependentMinimumKeepInfo = createBacking(capacity); + } + + public static DependentMinimumKeepInfoCollection create() { + return new HashDependentMinimumKeepInfoCollection(); + } + + public static DependentMinimumKeepInfoCollection create(int capacity) { + return new HashDependentMinimumKeepInfoCollection(capacity); + } + + public static DependentMinimumKeepInfoCollection createConcurrent() { + return new ConcurrentDependentMinimumKeepInfoCollection(); + } + + public abstract Map createBacking(); + + public abstract Map createBacking(int capacity); + + public abstract MinimumKeepInfoCollection createInnerBacking(); + public void forEach(BiConsumer consumer) { dependentMinimumKeepInfo.forEach(consumer); } @@ -62,7 +85,7 @@ public MinimumKeepInfoCollection get(EnqueuerEvent preconditionEvent) { public MinimumKeepInfoCollection getOrCreateMinimumKeepInfoFor(EnqueuerEvent preconditionEvent) { return dependentMinimumKeepInfo.computeIfAbsent( - preconditionEvent, ignoreKey(MinimumKeepInfoCollection::new)); + preconditionEvent, ignoreKey(this::createInnerBacking)); } public Joiner getOrCreateMinimumKeepInfoFor( @@ -151,8 +174,7 @@ public KeepMethodInfo.Joiner remove(EnqueuerEvent preconditionEvent, DexMethod m public DependentMinimumKeepInfoCollection rewrittenWithLens(GraphLens graphLens, Timing timing) { timing.begin("Rewrite DependentMinimumKeepInfoCollection"); - DependentMinimumKeepInfoCollection rewrittenDependentMinimumKeepInfo = - new DependentMinimumKeepInfoCollection(); + DependentMinimumKeepInfoCollection rewrittenDependentMinimumKeepInfo = create(size()); forEach( (preconditionEvent, minimumKeepInfo) -> { EnqueuerEvent rewrittenPreconditionEvent = preconditionEvent.rewrittenWithLens(graphLens); @@ -165,4 +187,54 @@ public DependentMinimumKeepInfoCollection rewrittenWithLens(GraphLens graphLens, timing.end(); return rewrittenDependentMinimumKeepInfo; } + + public int size() { + return dependentMinimumKeepInfo.size(); + } + + private static class ConcurrentDependentMinimumKeepInfoCollection + extends DependentMinimumKeepInfoCollection { + + @Override + public Map createBacking() { + return new ConcurrentHashMap<>(); + } + + @Override + public Map createBacking(int capacity) { + return new ConcurrentHashMap<>(capacity); + } + + @Override + public MinimumKeepInfoCollection createInnerBacking() { + return MinimumKeepInfoCollection.createConcurrent(); + } + } + + private static class HashDependentMinimumKeepInfoCollection + extends DependentMinimumKeepInfoCollection { + + HashDependentMinimumKeepInfoCollection() { + super(); + } + + HashDependentMinimumKeepInfoCollection(int capacity) { + super(capacity); + } + + @Override + public Map createBacking() { + return new HashMap<>(); + } + + @Override + public Map createBacking(int capacity) { + return new HashMap<>(capacity); + } + + @Override + public MinimumKeepInfoCollection createInnerBacking() { + return MinimumKeepInfoCollection.create(); + } + } } diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index 86cdb5a263..e01f063888 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java @@ -400,7 +400,7 @@ public boolean isWhyAreYouKeeping() { * the outermost {@link EnqueuerEvent} is triggered during tracing (e.g., class X becomes live). */ private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo = - new DependentMinimumKeepInfoCollection(); + DependentMinimumKeepInfoCollection.createConcurrent(); /** * A set of seen const-class references that serve as an initial lock-candidate set and will diff --git a/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java index f0ca7f974b..a2d6e00798 100644 --- a/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java @@ -21,6 +21,7 @@ import com.android.tools.r8.shaking.KeepInfo.Joiner; import com.android.tools.r8.utils.MapUtils; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; @@ -34,14 +35,22 @@ public class MinimumKeepInfoCollection { private final Map> minimumKeepInfo; - public MinimumKeepInfoCollection() { - this(new ConcurrentHashMap<>()); - } - private MinimumKeepInfoCollection(Map> minimumKeepInfo) { this.minimumKeepInfo = minimumKeepInfo; } + public static MinimumKeepInfoCollection create() { + return new MinimumKeepInfoCollection(new IdentityHashMap<>()); + } + + public static MinimumKeepInfoCollection create(int capacity) { + return new MinimumKeepInfoCollection(new IdentityHashMap<>(capacity)); + } + + public static MinimumKeepInfoCollection createConcurrent() { + return new MinimumKeepInfoCollection(new ConcurrentHashMap<>()); + } + public static MinimumKeepInfoCollection empty() { return EMPTY; } @@ -154,7 +163,7 @@ public KeepMethodInfo.Joiner remove(DexMethod method) { } public MinimumKeepInfoCollection rewrittenWithLens(GraphLens graphLens) { - MinimumKeepInfoCollection rewrittenMinimumKeepInfo = new MinimumKeepInfoCollection(); + MinimumKeepInfoCollection rewrittenMinimumKeepInfo = create(size()); forEach( (reference, minimumKeepInfoForReference) -> { DexReference rewrittenReference = @@ -179,4 +188,8 @@ public MinimumKeepInfoCollection rewrittenWithLens(GraphLens graphLens) { }); return rewrittenMinimumKeepInfo; } + + public int size() { + return minimumKeepInfo.size(); + } } diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java index 58567a6bc1..ad7c3881a5 100644 --- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java +++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java @@ -116,7 +116,7 @@ public static class RootSetBuilder { private final DirectMappedDexApplication application; private final Iterable rules; private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo = - new DependentMinimumKeepInfoCollection(); + DependentMinimumKeepInfoCollection.createConcurrent(); private final LinkedHashMap reasonAsked = new LinkedHashMap<>(); private final Set alwaysInline = Sets.newIdentityHashSet(); private final Set neverInlineDueToSingleCaller = Sets.newIdentityHashSet(); From 0ff19c8d889d224cb12f92660d8869d3cd552de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gjesse?= Date: Fri, 9 Jun 2023 14:28:30 +0200 Subject: [PATCH 068/153] Update documentation for compiledump Include flag to turn off Gradle build cache. Using the build cache can result in build avoidance of the compiler invocation. Change-Id: I7b1c3f6fefb2ab6f90b1e06c1c4a82d3b8807c46 --- doc/compilerdump.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/compilerdump.md b/doc/compilerdump.md index 2db569a6fd..7bffd27c18 100644 --- a/doc/compilerdump.md +++ b/doc/compilerdump.md @@ -47,7 +47,7 @@ build command. This can typically be done by amending the command-line gradle build-target command. Say your build target is `assembleRelease`, you would run: ``` -./gradlew assembleRelease -Dcom.android.tools.r8.dumpinputtofile=mydump.zip --no-daemon +./gradlew assembleRelease -Dorg.gradle.caching=false -Dcom.android.tools.r8.dumpinputtofile=mydump.zip --no-daemon ``` If the build is a debug build, such as `assembleDebug`, then it will likely be an @@ -57,7 +57,7 @@ problematic compilation (if the compilation fails, the interesting dump will hopefully be the last dump): ``` -./gradlew assembleDebug -Dcom.android.tools.r8.dumpinputtodirectory=mydumps/ --no-daemon +./gradlew assembleDebug -Dorg.gradle.caching=false -Dcom.android.tools.r8.dumpinputtodirectory=mydumps/ --no-daemon ``` From b3280fdce27b044a34a74ee0820875e4b1418169 Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Mon, 12 Jun 2023 08:20:12 +0200 Subject: [PATCH 069/153] Fix for update to VerticalClassMergingTest Change-Id: I15da551a04a41d6ea59e9e1f4312dfe34bb7a7f2 --- .../r8/classmerging/vertical/VerticalClassMergerTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java index 9826428ebc..e01af0f69f 100644 --- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java @@ -403,6 +403,7 @@ public void testNestedDefaultInterfaceMethodsTest() throws Throwable { @Test public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Throwable { + assumeFalse("Ignore on CF due to invalid invokespecial", parameters.isCfRuntime()); String main = "classmerging.NestedDefaultInterfaceMethodsTest"; Path[] programFiles = new Path[] { @@ -801,12 +802,14 @@ public void testSuperCallNotRewrittenToDirect() throws Throwable { "SuperClassWithReferencedMethod.referencedMethod()"); testForD8(parameters.getBackend()) + .applyIf(parameters.isDexRuntime(), b -> b.setMinApi(parameters)) .addProgramFiles(programFiles) .addProgramDexFileData(smaliBuilder.compile()) .run(parameters.getRuntime(), main) .assertSuccessWithOutput(expectedOutput); testForR8(parameters.getBackend()) + .setMinApi(parameters) .addOptionsModification(this::configure) .addKeepMainRule(main) // Keep the classes to avoid merge, but don't keep methods which allows inlining. @@ -1215,7 +1218,8 @@ private R8TestCompileResult runTestOnInput( // Check that the R8-generated code produces the same result as D8-generated code. String d8Result = - testForD8() + testForD8(parameters.getBackend()) + .applyIf(parameters.isDexRuntime(), b -> b.setMinApi(parameters)) .addProgramResourceProviders(input.getProgramResourceProviders()) .run(parameters.getRuntime(), main) .assertSuccess() From e7f69c258ee90b9dbc26bbdd344f040ca62c6a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 8 Jun 2023 14:38:45 +0200 Subject: [PATCH 070/153] Split throw catch rewritings from CodeRewriter Bug: b/284304606 Change-Id: I538c7715b59c872d3c2346990439393beb86c7fc --- .../tools/r8/ir/conversion/IRConverter.java | 7 +- .../passes/ThrowCatchOptimizer.java | 456 ++++++++++++++++++ .../tools/r8/ir/optimize/CodeRewriter.java | 416 ---------------- .../shaking/EnqueuerDeferredTracingImpl.java | 3 +- 4 files changed, 462 insertions(+), 420 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index aa7b3714d1..cba30d979b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -33,6 +33,7 @@ import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; +import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer; import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; @@ -770,7 +771,7 @@ Timing optimize( new SparseConditionalConstantPropagation(appView, code).run(); timing.end(); timing.begin("Rewrite always throwing instructions"); - codeRewriter.optimizeAlwaysThrowingInstructions(code); + new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(code); timing.end(); timing.begin("Simplify control flow"); if (new BranchSimplifier(appView).simplifyBranches(code)) { @@ -801,7 +802,7 @@ Timing optimize( if (!isDebugMode) { timing.begin("Rewrite throw NPE"); - codeRewriter.rewriteThrowNullPointerException(code); + new ThrowCatchOptimizer(appView).rewriteThrowNullPointerException(code); timing.end(); previous = printMethod(code, "IR after rewrite throw null (SSA)", previous); } @@ -933,7 +934,7 @@ Timing optimize( } timing.begin("Redundant catch/rethrow elimination"); - codeRewriter.optimizeRedundantCatchRethrowInstructions(code); + new ThrowCatchOptimizer(appView).optimizeRedundantCatchRethrowInstructions(code); timing.end(); previous = printMethod(code, "IR after redundant catch/rethrow elimination (SSA)", previous); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java new file mode 100644 index 0000000000..8b170c5900 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java @@ -0,0 +1,456 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; +import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.CatchHandlers; +import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; +import com.android.tools.r8.ir.code.Goto; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.If; +import com.android.tools.r8.ir.code.InstanceFieldInstruction; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeDirect; +import com.android.tools.r8.ir.code.InvokeMethod; +import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; +import com.android.tools.r8.ir.code.InvokeVirtual; +import com.android.tools.r8.ir.code.NewInstance; +import com.android.tools.r8.ir.code.Phi; +import com.android.tools.r8.ir.code.Position; +import com.android.tools.r8.ir.code.Throw; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; +import java.util.BitSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +public class ThrowCatchOptimizer { + + private final AppView appView; + private final DexItemFactory dexItemFactory; + + public ThrowCatchOptimizer(AppView appView) { + this.appView = appView; + this.dexItemFactory = appView.dexItemFactory(); + } + + // Rewrite 'throw new NullPointerException()' to 'throw null'. + public void rewriteThrowNullPointerException(IRCode code) { + boolean shouldRemoveUnreachableBlocks = false; + for (BasicBlock block : code.blocks) { + InstructionListIterator it = block.listIterator(code); + while (it.hasNext()) { + Instruction instruction = it.next(); + + // Check for the patterns 'if (x == null) throw null' and + // 'if (x == null) throw new NullPointerException()'. + if (instruction.isIf()) { + if (appView + .dexItemFactory() + .objectsMethods + .isRequireNonNullMethod(code.method().getReference())) { + continue; + } + + If ifInstruction = instruction.asIf(); + if (!ifInstruction.isZeroTest()) { + continue; + } + + Value value = ifInstruction.lhs(); + if (!value.getType().isReferenceType()) { + assert value.getType().isPrimitiveType(); + continue; + } + + BasicBlock valueIsNullTarget = ifInstruction.targetFromCondition(0); + if (valueIsNullTarget.getPredecessors().size() != 1 + || !valueIsNullTarget.exit().isThrow()) { + continue; + } + + Throw throwInstruction = valueIsNullTarget.exit().asThrow(); + Value exceptionValue = throwInstruction.exception().getAliasedValue(); + if (!exceptionValue.isConstZero() + && exceptionValue.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) { + NewInstance newInstance = exceptionValue.definition.asNewInstance(); + if (newInstance.clazz != dexItemFactory.npeType) { + continue; + } + if (newInstance.outValue().numberOfAllUsers() != 2) { + continue; // Could be mutated before it is thrown. + } + InvokeDirect constructorCall = newInstance.getUniqueConstructorInvoke(dexItemFactory); + if (constructorCall == null) { + continue; + } + if (constructorCall.getInvokedMethod() != dexItemFactory.npeMethods.init) { + continue; + } + } else if (!exceptionValue.isConstZero()) { + continue; + } + + boolean canDetachValueIsNullTarget = true; + for (Instruction i : valueIsNullTarget.instructionsBefore(throwInstruction)) { + if (!i.isBlockLocalInstructionWithoutSideEffects(appView, code.context())) { + canDetachValueIsNullTarget = false; + break; + } + } + if (!canDetachValueIsNullTarget) { + continue; + } + + insertNotNullCheck( + block, + it, + ifInstruction, + ifInstruction.targetFromCondition(1), + valueIsNullTarget, + throwInstruction.getPosition()); + shouldRemoveUnreachableBlocks = true; + } + + // Check for 'new-instance NullPointerException' with 2 users, not declaring a local and + // not ending the scope of any locals. + if (instruction.isNewInstance() + && instruction.asNewInstance().clazz == dexItemFactory.npeType + && instruction.outValue().numberOfAllUsers() == 2 + && !instruction.outValue().hasLocalInfo() + && instruction.getDebugValues().isEmpty()) { + if (it.hasNext()) { + Instruction instruction2 = it.next(); + // Check for 'invoke NullPointerException.init() not ending the scope of any locals + // and with the result of the first instruction as the argument. Also check that + // the two first instructions have the same position. + if (instruction2.isInvokeDirect() && instruction2.getDebugValues().isEmpty()) { + InvokeDirect invokeDirect = instruction2.asInvokeDirect(); + if (invokeDirect.getInvokedMethod() == dexItemFactory.npeMethods.init + && invokeDirect.getReceiver() == instruction.outValue() + && invokeDirect.arguments().size() == 1 + && invokeDirect.getPosition() == instruction.getPosition()) { + if (it.hasNext()) { + Instruction instruction3 = it.next(); + // Finally check that the last instruction is a throw of the initialized + // exception object and replace with 'throw null' if so. + if (instruction3.isThrow() + && instruction3.asThrow().exception() == instruction.outValue()) { + // Create const 0 with null type and a throw using that value. + Instruction nullPointer = code.createConstNull(); + Instruction throwInstruction = new Throw(nullPointer.outValue()); + // Preserve positions: we have checked that the first two original instructions + // have the same position. + assert instruction.getPosition() == instruction2.getPosition(); + nullPointer.setPosition(instruction.getPosition()); + throwInstruction.setPosition(instruction3.getPosition()); + // Copy debug values from original throw to new throw to correctly end scope + // of locals. + instruction3.moveDebugValues(throwInstruction); + // Remove the three original instructions. + it.remove(); + it.previous(); + it.remove(); + it.previous(); + it.remove(); + // Replace them with 'const 0' and 'throw'. + it.add(nullPointer); + it.add(throwInstruction); + } + } + } + } + } + } + } + } + if (shouldRemoveUnreachableBlocks) { + Set affectedValues = code.removeUnreachableBlocks(); + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } + } + assert code.isConsistentSSA(appView); + } + + // Find all instructions that always throw, split the block after each such instruction and follow + // it with a block throwing a null value (which should result in NPE). Note that this throw is not + // expected to be ever reached, but is intended to satisfy verifier. + public void optimizeAlwaysThrowingInstructions(IRCode code) { + Set affectedValues = Sets.newIdentityHashSet(); + Set blocksToRemove = Sets.newIdentityHashSet(); + ListIterator blockIterator = code.listIterator(); + ProgramMethod context = code.context(); + boolean hasUnlinkedCatchHandlers = false; + // For cyclic phis we sometimes do not propagate the dynamic upper type after rewritings. + // The inValue.isAlwaysNull(appView) check below will not recompute the dynamic type of phi's + // so we recompute all phis here if they are always null. + AppView appViewWithClassHierarchy = + appView.hasClassHierarchy() ? appView.withClassHierarchy() : null; + if (appViewWithClassHierarchy != null) { + code.blocks.forEach( + block -> + block + .getPhis() + .forEach( + phi -> { + if (!phi.getType().isDefinitelyNull()) { + TypeElement dynamicUpperBoundType = + phi.getDynamicUpperBoundType(appViewWithClassHierarchy); + if (dynamicUpperBoundType.isDefinitelyNull()) { + affectedValues.add(phi); + phi.setType(dynamicUpperBoundType); + } + } + })); + } + while (blockIterator.hasNext()) { + BasicBlock block = blockIterator.next(); + if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) { + continue; + } + if (blocksToRemove.contains(block)) { + continue; + } + InstructionListIterator instructionIterator = block.listIterator(code); + while (instructionIterator.hasNext()) { + Instruction instruction = instructionIterator.next(); + if (instruction.throwsOnNullInput()) { + Value inValue = instruction.getNonNullInput(); + if (inValue.isAlwaysNull(appView)) { + // Insert `throw null` after the instruction if it is not guaranteed to throw an NPE. + if (instruction.isAssume()) { + // If this assume is in a block with catch handlers, then the out-value can have + // usages in the catch handler if the block's throwing instruction comes after the + // assume instruction. In this case, the catch handler is also guaranteed to be dead, + // so we detach it from the current block. + if (block.hasCatchHandlers() + && block.isInstructionBeforeThrowingInstruction(instruction)) { + for (CatchHandler catchHandler : block.getCatchHandlers()) { + catchHandler.getTarget().unlinkCatchHandler(); + } + hasUnlinkedCatchHandlers = true; + } + } else if (instruction.isInstanceFieldInstruction()) { + InstanceFieldInstruction instanceFieldInstruction = + instruction.asInstanceFieldInstruction(); + if (instanceFieldInstruction.instructionInstanceCanThrow( + appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) { + instructionIterator.next(); + } + } else if (instruction.isInvokeMethodWithReceiver()) { + InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver(); + SideEffectAssumption assumption = + SideEffectAssumption.RECEIVER_NOT_NULL.join( + SideEffectAssumption.INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS); + if (invoke.instructionMayHaveSideEffects(appView, context, assumption)) { + instructionIterator.next(); + } + } + instructionIterator.replaceCurrentInstructionWithThrowNull( + appView, code, blockIterator, blocksToRemove, affectedValues); + continue; + } + } + + if (!instruction.isInvokeMethod()) { + continue; + } + + InvokeMethod invoke = instruction.asInvokeMethod(); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context()); + if (singleTarget == null) { + continue; + } + + MethodOptimizationInfo optimizationInfo = + singleTarget.getDefinition().getOptimizationInfo(); + + // If the invoke instruction is a null check, we can remove it. + boolean isNullCheck = false; + if (optimizationInfo.hasNonNullParamOrThrow()) { + BitSet nonNullParamOrThrow = optimizationInfo.getNonNullParamOrThrow(); + for (int i = 0; i < invoke.arguments().size(); i++) { + Value argument = invoke.arguments().get(i); + if (argument.isAlwaysNull(appView) && nonNullParamOrThrow.get(i)) { + isNullCheck = true; + break; + } + } + } + // If the invoke instruction never returns normally, we can insert a throw null instruction + // after the invoke. + if (isNullCheck || optimizationInfo.neverReturnsNormally()) { + instructionIterator.setInsertionPosition(invoke.getPosition()); + instructionIterator.next(); + instructionIterator.replaceCurrentInstructionWithThrowNull( + appView, code, blockIterator, blocksToRemove, affectedValues); + instructionIterator.unsetInsertionPosition(); + } + } + } + code.removeBlocks(blocksToRemove); + if (hasUnlinkedCatchHandlers) { + affectedValues.addAll(code.removeUnreachableBlocks()); + } + assert code.getUnreachableBlocks().isEmpty(); + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } + assert code.isConsistentSSA(appView); + } + + // Find any case where we have a catch followed immediately and only by a rethrow. This is extra + // code doing what the JVM does automatically and can be safely elided. + public void optimizeRedundantCatchRethrowInstructions(IRCode code) { + ListIterator blockIterator = code.listIterator(); + boolean hasUnlinkedCatchHandlers = false; + while (blockIterator.hasNext()) { + BasicBlock blockWithHandlers = blockIterator.next(); + if (blockWithHandlers.hasCatchHandlers()) { + boolean allHandlersAreTrivial = true; + for (CatchHandler handler : blockWithHandlers.getCatchHandlers()) { + if (!isSingleHandlerTrivial(handler.target, code)) { + allHandlersAreTrivial = false; + break; + } + } + // We need to ensure all handlers are trivial to unlink, since if one is non-trivial, and + // its guard is a parent type to a trivial one, removing the trivial catch will result in + // us hitting the non-trivial catch. This could be avoided by more sophisticated type + // analysis. + if (allHandlersAreTrivial) { + hasUnlinkedCatchHandlers = true; + for (CatchHandler handler : blockWithHandlers.getCatchHandlers()) { + handler.getTarget().unlinkCatchHandler(); + } + } + } + } + if (hasUnlinkedCatchHandlers) { + code.removeUnreachableBlocks(); + } + } + + private boolean isSingleHandlerTrivial(BasicBlock firstBlock, IRCode code) { + InstructionListIterator instructionIterator = firstBlock.listIterator(code); + Instruction instruction = instructionIterator.next(); + if (!instruction.isMoveException()) { + // A catch handler which doesn't use its exception is not going to be a trivial rethrow. + return false; + } + Value exceptionValue = instruction.outValue(); + if (!isPotentialTrivialRethrowValue(exceptionValue)) { + return false; + } + while (instructionIterator.hasNext()) { + instruction = instructionIterator.next(); + BasicBlock currentBlock = instruction.getBlock(); + if (instruction.isGoto()) { + BasicBlock nextBlock = instruction.asGoto().getTarget(); + int predecessorIndex = nextBlock.getPredecessors().indexOf(currentBlock); + Value phiAliasOfExceptionValue = null; + for (Phi phi : nextBlock.getPhis()) { + Value operand = phi.getOperand(predecessorIndex); + if (exceptionValue == operand) { + phiAliasOfExceptionValue = phi; + break; + } + } + if (phiAliasOfExceptionValue != null) { + if (!isPotentialTrivialRethrowValue(phiAliasOfExceptionValue)) { + return false; + } + exceptionValue = phiAliasOfExceptionValue; + } + instructionIterator = nextBlock.listIterator(code); + } else if (instruction.isThrow()) { + List throwValues = instruction.inValues(); + assert throwValues.size() == 1; + if (throwValues.get(0) != exceptionValue) { + return false; + } + CatchHandlers currentHandlers = currentBlock.getCatchHandlers(); + if (!currentHandlers.isEmpty()) { + // This is the case where our trivial catch handler has catch handler(s). For now, we + // will only treat our block as trivial if all its catch handlers are also trivial. + // Note: it is possible that we could "bridge" a trivial handler, where we take the + // handlers of the handler and bring them up to replace the trivial handler. Example: + // catch (Throwable t) { + // try { throw t; } catch(Throwable abc) { foo(abc); } + // } + // could turn into: + // catch (Throwable abc) { + // foo(abc); + // } + // However this gets significantly harder when you have to consider non-matching guard + // types. + for (CatchHandler handler : currentHandlers) { + if (!isSingleHandlerTrivial(handler.getTarget(), code)) { + return false; + } + } + } + return true; + } else { + // Any other instructions in the catch handler means it's not trivial, and thus we can't + // elide. + return false; + } + } + throw new Unreachable("Triviality check should always return before the loop terminates"); + } + + private boolean isPotentialTrivialRethrowValue(Value exceptionValue) { + if (exceptionValue.hasDebugUsers()) { + return false; + } + if (exceptionValue.hasUsers()) { + if (exceptionValue.hasPhiUsers() + || !exceptionValue.hasSingleUniqueUser() + || !exceptionValue.singleUniqueUser().isThrow()) { + return false; + } + } else if (exceptionValue.numberOfPhiUsers() != 1) { + return false; + } + return true; + } + + private void insertNotNullCheck( + BasicBlock block, + InstructionListIterator iterator, + If theIf, + BasicBlock target, + BasicBlock deadTarget, + Position position) { + deadTarget.unlinkSinglePredecessorSiblingsAllowed(); + assert theIf == block.exit(); + iterator.previous(); + Instruction instruction; + DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass; + instruction = new InvokeVirtual(getClassMethod, null, ImmutableList.of(theIf.lhs())); + instruction.setPosition(position); + iterator.add(instruction); + iterator.next(); + iterator.replaceCurrentInstruction(new Goto()); + assert block.exit().isGoto(); + assert block.exit().asGoto().getTarget() == target; + } +} 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 4ad6534ed0..3a60f68b1d 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 @@ -14,7 +14,6 @@ import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DexClass; @@ -34,8 +33,6 @@ import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.Binop; -import com.android.tools.r8.ir.code.CatchHandlers; -import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; import com.android.tools.r8.ir.code.ConstClass; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; @@ -43,30 +40,23 @@ import com.android.tools.r8.ir.code.DebugLocalsChange; import com.android.tools.r8.ir.code.DexItemBasedConstString; import com.android.tools.r8.ir.code.DominatorTree; -import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; -import com.android.tools.r8.ir.code.InstanceFieldInstruction; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InstructionOrPhi; import com.android.tools.r8.ir.code.Invoke; -import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeInterface; import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Move; -import com.android.tools.r8.ir.code.NewInstance; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.SyntheticPosition; import com.android.tools.r8.ir.code.StaticGet; -import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; @@ -88,7 +78,6 @@ import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.ArrayList; -import java.util.BitSet; import java.util.Collections; import java.util.IdentityHashMap; import java.util.LinkedHashMap; @@ -173,146 +162,6 @@ public static void removeAssumeInstructions(AppView appView, IRCode code) { assert Streams.stream(code.instructions()).noneMatch(Instruction::isAssume); } - // Rewrite 'throw new NullPointerException()' to 'throw null'. - public void rewriteThrowNullPointerException(IRCode code) { - boolean shouldRemoveUnreachableBlocks = false; - for (BasicBlock block : code.blocks) { - InstructionListIterator it = block.listIterator(code); - while (it.hasNext()) { - Instruction instruction = it.next(); - - // Check for the patterns 'if (x == null) throw null' and - // 'if (x == null) throw new NullPointerException()'. - if (instruction.isIf()) { - if (appView - .dexItemFactory() - .objectsMethods - .isRequireNonNullMethod(code.method().getReference())) { - continue; - } - - If ifInstruction = instruction.asIf(); - if (!ifInstruction.isZeroTest()) { - continue; - } - - Value value = ifInstruction.lhs(); - if (!value.getType().isReferenceType()) { - assert value.getType().isPrimitiveType(); - continue; - } - - BasicBlock valueIsNullTarget = ifInstruction.targetFromCondition(0); - if (valueIsNullTarget.getPredecessors().size() != 1 - || !valueIsNullTarget.exit().isThrow()) { - continue; - } - - Throw throwInstruction = valueIsNullTarget.exit().asThrow(); - Value exceptionValue = throwInstruction.exception().getAliasedValue(); - if (!exceptionValue.isConstZero() - && exceptionValue.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) { - NewInstance newInstance = exceptionValue.definition.asNewInstance(); - if (newInstance.clazz != dexItemFactory.npeType) { - continue; - } - if (newInstance.outValue().numberOfAllUsers() != 2) { - continue; // Could be mutated before it is thrown. - } - InvokeDirect constructorCall = newInstance.getUniqueConstructorInvoke(dexItemFactory); - if (constructorCall == null) { - continue; - } - if (constructorCall.getInvokedMethod() != dexItemFactory.npeMethods.init) { - continue; - } - } else if (!exceptionValue.isConstZero()) { - continue; - } - - boolean canDetachValueIsNullTarget = true; - for (Instruction i : valueIsNullTarget.instructionsBefore(throwInstruction)) { - if (!i.isBlockLocalInstructionWithoutSideEffects(appView, code.context())) { - canDetachValueIsNullTarget = false; - break; - } - } - if (!canDetachValueIsNullTarget) { - continue; - } - - insertNotNullCheck( - block, - it, - ifInstruction, - ifInstruction.targetFromCondition(1), - valueIsNullTarget, - throwInstruction.getPosition()); - shouldRemoveUnreachableBlocks = true; - } - - // Check for 'new-instance NullPointerException' with 2 users, not declaring a local and - // not ending the scope of any locals. - if (instruction.isNewInstance() - && instruction.asNewInstance().clazz == dexItemFactory.npeType - && instruction.outValue().numberOfAllUsers() == 2 - && !instruction.outValue().hasLocalInfo() - && instruction.getDebugValues().isEmpty()) { - if (it.hasNext()) { - Instruction instruction2 = it.next(); - // Check for 'invoke NullPointerException.init() not ending the scope of any locals - // and with the result of the first instruction as the argument. Also check that - // the two first instructions have the same position. - if (instruction2.isInvokeDirect() - && instruction2.getDebugValues().isEmpty()) { - InvokeDirect invokeDirect = instruction2.asInvokeDirect(); - if (invokeDirect.getInvokedMethod() == dexItemFactory.npeMethods.init - && invokeDirect.getReceiver() == instruction.outValue() - && invokeDirect.arguments().size() == 1 - && invokeDirect.getPosition() == instruction.getPosition()) { - if (it.hasNext()) { - Instruction instruction3 = it.next(); - // Finally check that the last instruction is a throw of the initialized - // exception object and replace with 'throw null' if so. - if (instruction3.isThrow() - && instruction3.asThrow().exception() == instruction.outValue()) { - // Create const 0 with null type and a throw using that value. - Instruction nullPointer = code.createConstNull(); - Instruction throwInstruction = new Throw(nullPointer.outValue()); - // Preserve positions: we have checked that the first two original instructions - // have the same position. - assert instruction.getPosition() == instruction2.getPosition(); - nullPointer.setPosition(instruction.getPosition()); - throwInstruction.setPosition(instruction3.getPosition()); - // Copy debug values from original throw to new throw to correctly end scope - // of locals. - instruction3.moveDebugValues(throwInstruction); - // Remove the three original instructions. - it.remove(); - it.previous(); - it.remove(); - it.previous(); - it.remove(); - // Replace them with 'const 0' and 'throw'. - it.add(nullPointer); - it.add(throwInstruction); - } - } - } - } - } - } - } - } - if (shouldRemoveUnreachableBlocks) { - Set affectedValues = code.removeUnreachableBlocks(); - if (!affectedValues.isEmpty()) { - new TypeAnalysis(appView).narrowing(affectedValues); - } - } - assert code.isConsistentSSA(appView); - } - private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) { // TODO(sgjesse): Insert cast if required. TypeElement returnType = @@ -1237,271 +1086,6 @@ private boolean replaceDominatedConstNumbers( return changed; } - private boolean isPotentialTrivialRethrowValue(Value exceptionValue) { - if (exceptionValue.hasDebugUsers()) { - return false; - } - if (exceptionValue.hasUsers()) { - if (exceptionValue.hasPhiUsers() - || !exceptionValue.hasSingleUniqueUser() - || !exceptionValue.singleUniqueUser().isThrow()) { - return false; - } - } else if (exceptionValue.numberOfPhiUsers() != 1) { - return false; - } - return true; - } - - private boolean isSingleHandlerTrivial(BasicBlock firstBlock, IRCode code) { - InstructionListIterator instructionIterator = firstBlock.listIterator(code); - Instruction instruction = instructionIterator.next(); - if (!instruction.isMoveException()) { - // A catch handler which doesn't use its exception is not going to be a trivial rethrow. - return false; - } - Value exceptionValue = instruction.outValue(); - if (!isPotentialTrivialRethrowValue(exceptionValue)) { - return false; - } - while (instructionIterator.hasNext()) { - instruction = instructionIterator.next(); - BasicBlock currentBlock = instruction.getBlock(); - if (instruction.isGoto()) { - BasicBlock nextBlock = instruction.asGoto().getTarget(); - int predecessorIndex = nextBlock.getPredecessors().indexOf(currentBlock); - Value phiAliasOfExceptionValue = null; - for (Phi phi : nextBlock.getPhis()) { - Value operand = phi.getOperand(predecessorIndex); - if (exceptionValue == operand) { - phiAliasOfExceptionValue = phi; - break; - } - } - if (phiAliasOfExceptionValue != null) { - if (!isPotentialTrivialRethrowValue(phiAliasOfExceptionValue)) { - return false; - } - exceptionValue = phiAliasOfExceptionValue; - } - instructionIterator = nextBlock.listIterator(code); - } else if (instruction.isThrow()) { - List throwValues = instruction.inValues(); - assert throwValues.size() == 1; - if (throwValues.get(0) != exceptionValue) { - return false; - } - CatchHandlers currentHandlers = currentBlock.getCatchHandlers(); - if (!currentHandlers.isEmpty()) { - // This is the case where our trivial catch handler has catch handler(s). For now, we - // will only treat our block as trivial if all its catch handlers are also trivial. - // Note: it is possible that we could "bridge" a trivial handler, where we take the - // handlers of the handler and bring them up to replace the trivial handler. Example: - // catch (Throwable t) { - // try { throw t; } catch(Throwable abc) { foo(abc); } - // } - // could turn into: - // catch (Throwable abc) { - // foo(abc); - // } - // However this gets significantly harder when you have to consider non-matching guard - // types. - for (CatchHandler handler : currentHandlers) { - if (!isSingleHandlerTrivial(handler.getTarget(), code)) { - return false; - } - } - } - return true; - } else { - // Any other instructions in the catch handler means it's not trivial, and thus we can't - // elide. - return false; - } - } - throw new Unreachable("Triviality check should always return before the loop terminates"); - } - - // Find any case where we have a catch followed immediately and only by a rethrow. This is extra - // code doing what the JVM does automatically and can be safely elided. - public void optimizeRedundantCatchRethrowInstructions(IRCode code) { - ListIterator blockIterator = code.listIterator(); - boolean hasUnlinkedCatchHandlers = false; - while (blockIterator.hasNext()) { - BasicBlock blockWithHandlers = blockIterator.next(); - if (blockWithHandlers.hasCatchHandlers()) { - boolean allHandlersAreTrivial = true; - for (CatchHandler handler : blockWithHandlers.getCatchHandlers()) { - if (!isSingleHandlerTrivial(handler.target, code)) { - allHandlersAreTrivial = false; - break; - } - } - // We need to ensure all handlers are trivial to unlink, since if one is non-trivial, and - // its guard is a parent type to a trivial one, removing the trivial catch will result in - // us hitting the non-trivial catch. This could be avoided by more sophisticated type - // analysis. - if (allHandlersAreTrivial) { - hasUnlinkedCatchHandlers = true; - for (CatchHandler handler : blockWithHandlers.getCatchHandlers()) { - handler.getTarget().unlinkCatchHandler(); - } - } - } - } - if (hasUnlinkedCatchHandlers) { - code.removeUnreachableBlocks(); - } - } - - // Find all instructions that always throw, split the block after each such instruction and follow - // it with a block throwing a null value (which should result in NPE). Note that this throw is not - // expected to be ever reached, but is intended to satisfy verifier. - public void optimizeAlwaysThrowingInstructions(IRCode code) { - Set affectedValues = Sets.newIdentityHashSet(); - Set blocksToRemove = Sets.newIdentityHashSet(); - ListIterator blockIterator = code.listIterator(); - ProgramMethod context = code.context(); - boolean hasUnlinkedCatchHandlers = false; - // For cyclic phis we sometimes do not propagate the dynamic upper type after rewritings. - // The inValue.isAlwaysNull(appView) check below will not recompute the dynamic type of phi's - // so we recompute all phis here if they are always null. - AppView appViewWithClassHierarchy = - appView.hasClassHierarchy() ? appView.withClassHierarchy() : null; - if (appViewWithClassHierarchy != null) { - code.blocks.forEach( - block -> - block - .getPhis() - .forEach( - phi -> { - if (!phi.getType().isDefinitelyNull()) { - TypeElement dynamicUpperBoundType = - phi.getDynamicUpperBoundType(appViewWithClassHierarchy); - if (dynamicUpperBoundType.isDefinitelyNull()) { - affectedValues.add(phi); - phi.setType(dynamicUpperBoundType); - } - } - })); - } - while (blockIterator.hasNext()) { - BasicBlock block = blockIterator.next(); - if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) { - continue; - } - if (blocksToRemove.contains(block)) { - continue; - } - InstructionListIterator instructionIterator = block.listIterator(code); - while (instructionIterator.hasNext()) { - Instruction instruction = instructionIterator.next(); - if (instruction.throwsOnNullInput()) { - Value inValue = instruction.getNonNullInput(); - if (inValue.isAlwaysNull(appView)) { - // Insert `throw null` after the instruction if it is not guaranteed to throw an NPE. - if (instruction.isAssume()) { - // If this assume is in a block with catch handlers, then the out-value can have - // usages in the catch handler if the block's throwing instruction comes after the - // assume instruction. In this case, the catch handler is also guaranteed to be dead, - // so we detach it from the current block. - if (block.hasCatchHandlers() - && block.isInstructionBeforeThrowingInstruction(instruction)) { - for (CatchHandler catchHandler : block.getCatchHandlers()) { - catchHandler.getTarget().unlinkCatchHandler(); - } - hasUnlinkedCatchHandlers = true; - } - } else if (instruction.isInstanceFieldInstruction()) { - InstanceFieldInstruction instanceFieldInstruction = - instruction.asInstanceFieldInstruction(); - if (instanceFieldInstruction.instructionInstanceCanThrow( - appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) { - instructionIterator.next(); - } - } else if (instruction.isInvokeMethodWithReceiver()) { - InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver(); - SideEffectAssumption assumption = - SideEffectAssumption.RECEIVER_NOT_NULL.join( - SideEffectAssumption.INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS); - if (invoke.instructionMayHaveSideEffects(appView, context, assumption)) { - instructionIterator.next(); - } - } - instructionIterator.replaceCurrentInstructionWithThrowNull( - appView, code, blockIterator, blocksToRemove, affectedValues); - continue; - } - } - - if (!instruction.isInvokeMethod()) { - continue; - } - - InvokeMethod invoke = instruction.asInvokeMethod(); - DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context()); - if (singleTarget == null) { - continue; - } - - MethodOptimizationInfo optimizationInfo = - singleTarget.getDefinition().getOptimizationInfo(); - - // If the invoke instruction is a null check, we can remove it. - boolean isNullCheck = false; - if (optimizationInfo.hasNonNullParamOrThrow()) { - BitSet nonNullParamOrThrow = optimizationInfo.getNonNullParamOrThrow(); - for (int i = 0; i < invoke.arguments().size(); i++) { - Value argument = invoke.arguments().get(i); - if (argument.isAlwaysNull(appView) && nonNullParamOrThrow.get(i)) { - isNullCheck = true; - break; - } - } - } - // If the invoke instruction never returns normally, we can insert a throw null instruction - // after the invoke. - if (isNullCheck || optimizationInfo.neverReturnsNormally()) { - instructionIterator.setInsertionPosition(invoke.getPosition()); - instructionIterator.next(); - instructionIterator.replaceCurrentInstructionWithThrowNull( - appView, code, blockIterator, blocksToRemove, affectedValues); - instructionIterator.unsetInsertionPosition(); - } - } - } - code.removeBlocks(blocksToRemove); - if (hasUnlinkedCatchHandlers) { - affectedValues.addAll(code.removeUnreachableBlocks()); - } - assert code.getUnreachableBlocks().isEmpty(); - if (!affectedValues.isEmpty()) { - new TypeAnalysis(appView).narrowing(affectedValues); - } - assert code.isConsistentSSA(appView); - } - - private void insertNotNullCheck( - BasicBlock block, - InstructionListIterator iterator, - If theIf, - BasicBlock target, - BasicBlock deadTarget, - Position position) { - deadTarget.unlinkSinglePredecessorSiblingsAllowed(); - assert theIf == block.exit(); - iterator.previous(); - Instruction instruction; - DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass; - instruction = new InvokeVirtual(getClassMethod, null, ImmutableList.of(theIf.lhs())); - instruction.setPosition(position); - iterator.add(instruction); - iterator.next(); - iterator.replaceCurrentInstruction(new Goto()); - assert block.exit().isGoto(); - assert block.exit().asGoto().getTarget() == target; - } - /** * Remove moves that are not actually used by instructions in exiting paths. These moves can arise * due to debug local info needing a particular value and the live-interval for it then moves it diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java index 650aa8bcd3..1c0d3de9d7 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java @@ -25,6 +25,7 @@ import com.android.tools.r8.ir.conversion.IRToCfFinalizer; import com.android.tools.r8.ir.conversion.IRToDexFinalizer; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; +import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer; import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo; import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind; import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata; @@ -269,7 +270,7 @@ private void rewriteMethod( rewriter.rewriteCode(ir, initializedClassesWithContexts, prunedFields); // Run dead code elimination. - rewriter.getCodeRewriter().optimizeAlwaysThrowingInstructions(ir); + new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(ir); rewriter.getDeadCodeRemover().run(ir, Timing.empty()); // Finalize to class files or dex. From 23807b29dcef785d1ad8940573bbe3616f4df972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 8 Jun 2023 14:49:16 +0200 Subject: [PATCH 071/153] Split dex constant optimizer from the CodeRewriter Bug: b/284304606 Change-Id: I4663cafed4e79b930986e9803ceb3e90e8a0528c --- .../tools/r8/ir/conversion/IRConverter.java | 11 +- .../passes/DexConstantOptimizer.java | 493 ++++++++++++++++++ .../tools/r8/ir/optimize/CodeRewriter.java | 435 ---------------- 3 files changed, 496 insertions(+), 443 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index cba30d979b..bc82bc4011 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -31,6 +31,7 @@ import com.android.tools.r8.ir.conversion.passes.BinopRewriter; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; +import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer; @@ -883,14 +884,8 @@ Timing optimize( constantCanonicalizer.canonicalize(); timing.end(); previous = printMethod(code, "IR after constant canonicalization (SSA)", previous); - timing.begin("Create constants for literal instructions"); - codeRewriter.useDedicatedConstantForLitInstruction(code); - timing.end(); - previous = printMethod(code, "IR after constant literals (SSA)", previous); - timing.begin("Shorten live ranges"); - codeRewriter.shortenLiveRanges(code, constantCanonicalizer); - timing.end(); - previous = printMethod(code, "IR after shorten live ranges (SSA)", previous); + new DexConstantOptimizer(appView, constantCanonicalizer).run(context, code, timing); + previous = printMethod(code, "IR after dex constant optimization (SSA)", previous); } if (removeVerificationErrorForUnknownReturnedValues != null) { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java new file mode 100644 index 0000000000..1e00a5e51c --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java @@ -0,0 +1,493 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS; +import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER; +import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING; +import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING; +import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; +import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; + +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; +import com.android.tools.r8.ir.code.Binop; +import com.android.tools.r8.ir.code.ConstClass; +import com.android.tools.r8.ir.code.ConstNumber; +import com.android.tools.r8.ir.code.ConstString; +import com.android.tools.r8.ir.code.DexItemBasedConstString; +import com.android.tools.r8.ir.code.DominatorTree; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InstanceGet; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InstructionOrPhi; +import com.android.tools.r8.ir.code.Phi; +import com.android.tools.r8.ir.code.Position; +import com.android.tools.r8.ir.code.StaticGet; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.optimize.ConstantCanonicalizer; +import com.android.tools.r8.utils.LazyBox; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +public class DexConstantOptimizer extends CodeRewriterPass { + + // This constant was determined by experimentation. + private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50; + + private final ConstantCanonicalizer constantCanonicalizer; + + public DexConstantOptimizer(AppView appView, ConstantCanonicalizer constantCanonicalizer) { + super(appView); + this.constantCanonicalizer = constantCanonicalizer; + } + + @Override + String getTimingId() { + return "DexConstantOptimizer"; + } + + @Override + void rewriteCode(ProgramMethod method, IRCode code) { + useDedicatedConstantForLitInstruction(code); + shortenLiveRanges(code, constantCanonicalizer); + } + + @Override + boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + return true; + } + + /** + * If an instruction is known to be a binop/lit8 or binop//lit16 instruction, update the + * instruction to use its own constant that will be defined just before the instruction. This + * transformation allows to decrease pressure on register allocation by defining the shortest + * range of constant used by this kind of instruction. D8 knowns at build time that constant will + * be encoded directly into the final Dex instruction. + */ + public void useDedicatedConstantForLitInstruction(IRCode code) { + if (!code.metadata().mayHaveArithmeticOrLogicalBinop()) { + return; + } + + for (BasicBlock block : code.blocks) { + InstructionListIterator instructionIterator = block.listIterator(code); + // Collect all the non constant in values for binop/lit8 or binop/lit16 instructions. + Set binopsWithLit8OrLit16NonConstantValues = Sets.newIdentityHashSet(); + while (instructionIterator.hasNext()) { + Instruction currentInstruction = instructionIterator.next(); + if (!isBinopWithLit8OrLit16(currentInstruction)) { + continue; + } + Value value = binopWithLit8OrLit16NonConstant(currentInstruction.asBinop()); + assert value != null; + binopsWithLit8OrLit16NonConstantValues.add(value); + } + if (binopsWithLit8OrLit16NonConstantValues.isEmpty()) { + continue; + } + // Find last use in block of all the non constant in values for binop/lit8 or binop/lit16 + // instructions. + Reference2IntMap lastUseOfBinopsWithLit8OrLit16NonConstantValues = + new Reference2IntOpenHashMap<>(); + lastUseOfBinopsWithLit8OrLit16NonConstantValues.defaultReturnValue(-1); + int currentInstructionNumber = block.getInstructions().size(); + while (instructionIterator.hasPrevious()) { + Instruction currentInstruction = instructionIterator.previous(); + currentInstructionNumber--; + for (Value value : + Iterables.concat(currentInstruction.inValues(), currentInstruction.getDebugValues())) { + if (!binopsWithLit8OrLit16NonConstantValues.contains(value)) { + continue; + } + if (!lastUseOfBinopsWithLit8OrLit16NonConstantValues.containsKey(value)) { + lastUseOfBinopsWithLit8OrLit16NonConstantValues.put(value, currentInstructionNumber); + } + } + } + // Do the transformation except if the binop can use the binop/2addr format. + currentInstructionNumber--; + assert currentInstructionNumber == -1; + while (instructionIterator.hasNext()) { + Instruction currentInstruction = instructionIterator.next(); + currentInstructionNumber++; + if (!isBinopWithLit8OrLit16(currentInstruction)) { + continue; + } + Binop binop = currentInstruction.asBinop(); + if (!canBe2AddrInstruction( + binop, currentInstructionNumber, lastUseOfBinopsWithLit8OrLit16NonConstantValues)) { + Value constValue = binopWithLit8OrLit16Constant(currentInstruction); + if (constValue.numberOfAllUsers() > 1) { + // No need to do the transformation if the const value is already used only one time. + ConstNumber newConstant = + ConstNumber.copyOf(code, constValue.definition.asConstNumber()); + newConstant.setPosition(currentInstruction.getPosition()); + newConstant.setBlock(currentInstruction.getBlock()); + currentInstruction.replaceValue(constValue, newConstant.outValue()); + constValue.removeUser(currentInstruction); + instructionIterator.previous(); + instructionIterator.add(newConstant); + instructionIterator.next(); + } + } + } + } + + assert code.isConsistentSSA(appView); + } + + // Check if a binop can be represented in the binop/lit8 or binop/lit16 form. + private static boolean isBinopWithLit8OrLit16(Instruction instruction) { + if (!instruction.isArithmeticBinop() && !instruction.isLogicalBinop()) { + return false; + } + Binop binop = instruction.asBinop(); + // If one of the values does not need a register it is implicitly a binop/lit8 or binop/lit16. + boolean result = + !binop.needsValueInRegister(binop.leftValue()) + || !binop.needsValueInRegister(binop.rightValue()); + assert !result || binop.leftValue().isConstNumber() || binop.rightValue().isConstNumber(); + return result; + } + + // Return the constant in-value of a binop/lit8 or binop/lit16 instruction. + private static Value binopWithLit8OrLit16Constant(Instruction instruction) { + assert isBinopWithLit8OrLit16(instruction); + Binop binop = instruction.asBinop(); + if (binop.leftValue().isConstNumber()) { + return binop.leftValue(); + } else if (binop.rightValue().isConstNumber()) { + return binop.rightValue(); + } else { + throw new Unreachable(); + } + } + + // Return the non-constant in-value of a binop/lit8 or binop/lit16 instruction. + private static Value binopWithLit8OrLit16NonConstant(Binop binop) { + if (binop.leftValue().isConstNumber()) { + return binop.rightValue(); + } else if (binop.rightValue().isConstNumber()) { + return binop.leftValue(); + } else { + throw new Unreachable(); + } + } + + /** + * Estimate if a binary operation can be a binop/2addr form or not. It can be a 2addr form when an + * argument is no longer needed after the binary operation and can be overwritten. That is + * definitely the case if there is no path between the binary operation and all other usages. + */ + private static boolean canBe2AddrInstruction( + Binop binop, int binopInstructionNumber, Reference2IntMap lastUseOfRelevantValue) { + Value value = binopWithLit8OrLit16NonConstant(binop); + assert value != null; + int lastUseInstructionNumber = lastUseOfRelevantValue.getInt(value); + // The binop instruction is a user, so there is always a last use in the block. + assert lastUseInstructionNumber != -1; + if (lastUseInstructionNumber > binopInstructionNumber) { + return false; + } + + Set noPathTo = Sets.newIdentityHashSet(); + BasicBlock binopBlock = binop.getBlock(); + Iterable users = + value.debugUsers() != null + ? Iterables.concat(value.uniqueUsers(), value.debugUsers(), value.uniquePhiUsers()) + : Iterables.concat(value.uniqueUsers(), value.uniquePhiUsers()); + for (InstructionOrPhi user : users) { + BasicBlock userBlock = user.getBlock(); + if (userBlock == binopBlock) { + // All users in the current block are either before the binop instruction or the + // binop instruction itself. + continue; + } + if (noPathTo.contains(userBlock)) { + continue; + } + if (binopBlock.hasPathTo(userBlock)) { + return false; + } + noPathTo.add(userBlock); + } + + return true; + } + + public void shortenLiveRanges(IRCode code, ConstantCanonicalizer canonicalizer) { + if (options.testing.disableShortenLiveRanges) { + return; + } + LazyBox dominatorTreeMemoization = new LazyBox<>(() -> new DominatorTree(code)); + Map> addConstantInBlock = new IdentityHashMap<>(); + LinkedList blocks = code.blocks; + for (BasicBlock block : blocks) { + shortenLiveRangesInsideBlock( + code, + block, + dominatorTreeMemoization, + addConstantInBlock, + canonicalizer::isConstantCanonicalizationCandidate); + } + + // Heuristic to decide if constant instructions are shared in dominator block + // of usages or moved to the usages. + + // Process all blocks in stable order to avoid non-determinism of hash map iterator. + BasicBlockIterator blockIterator = code.listIterator(); + while (blockIterator.hasNext()) { + BasicBlock block = blockIterator.next(); + Map movedInstructions = addConstantInBlock.get(block); + if (movedInstructions == null) { + continue; + } + assert !movedInstructions.isEmpty(); + if (!block.isEntry() && movedInstructions.size() > STOP_SHARED_CONSTANT_THRESHOLD) { + // If there are too many constant numbers in the same block they are copied rather than + // shared unless used by a phi. + movedInstructions + .values() + .removeIf( + movedInstruction -> { + if (movedInstruction.outValue().hasPhiUsers() + || !movedInstruction.isConstNumber()) { + return false; + } + ConstNumber constNumber = movedInstruction.asConstNumber(); + Value constantValue = movedInstruction.outValue(); + for (Instruction user : constantValue.uniqueUsers()) { + ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber); + newCstNum.setPosition(user.getPosition()); + InstructionListIterator iterator = user.getBlock().listIterator(code, user); + iterator.previous(); + iterator.add(newCstNum); + user.replaceValue(constantValue, newCstNum.outValue()); + } + constantValue.clearUsers(); + return true; + }); + } + + // Add constant into the dominator block of usages. + boolean hasCatchHandlers = block.hasCatchHandlers(); + InstructionListIterator instructionIterator = block.listIterator(code); + while (instructionIterator.hasNext()) { + Instruction insertionPoint = instructionIterator.next(); + if (insertionPoint.isJumpInstruction() + || (hasCatchHandlers && insertionPoint.instructionTypeCanThrow()) + || (options.canHaveCmpIfFloatBug() && insertionPoint.isCmp())) { + break; + } + for (Value use : + Iterables.concat(insertionPoint.inValues(), insertionPoint.getDebugValues())) { + Instruction movedInstruction = movedInstructions.remove(use); + if (movedInstruction != null) { + instructionIterator = + insertInstructionWithShortenedLiveRange( + code, blockIterator, instructionIterator, movedInstruction, insertionPoint); + } + } + } + + // Insert remaining constant instructions prior to the "exit". + Instruction insertionPoint = instructionIterator.peekPrevious(); + for (Instruction movedInstruction : movedInstructions.values()) { + instructionIterator = + insertInstructionWithShortenedLiveRange( + code, blockIterator, instructionIterator, movedInstruction, insertionPoint); + } + } + + assert code.isConsistentSSA(appView); + } + + private InstructionListIterator insertInstructionWithShortenedLiveRange( + IRCode code, + BasicBlockIterator blockIterator, + InstructionListIterator instructionIterator, + Instruction movedInstruction, + Instruction insertionPoint) { + Instruction previous = instructionIterator.previous(); + assert previous == insertionPoint; + movedInstruction.setPosition( + getPositionForMovedNonThrowingInstruction(movedInstruction, insertionPoint)); + if (movedInstruction.instructionTypeCanThrow() + && insertionPoint.getBlock().hasCatchHandlers()) { + // Split the block and reset the block iterator. + BasicBlock splitBlock = + instructionIterator.splitCopyCatchHandlers(code, blockIterator, appView.options()); + BasicBlock previousBlock = blockIterator.previousUntil(b -> b == splitBlock); + assert previousBlock == splitBlock; + blockIterator.next(); + + // Add the constant instruction before the exit instruction. + assert !instructionIterator.hasNext(); + instructionIterator.previous(); + instructionIterator.add(movedInstruction); + + // Continue insertion at the entry of the split block. + instructionIterator = splitBlock.listIterator(code); + } else { + instructionIterator.add(movedInstruction); + } + Instruction next = instructionIterator.next(); + assert next == insertionPoint; + return instructionIterator; + } + + private Position getPositionForMovedNonThrowingInstruction( + Instruction movedInstruction, Instruction insertionPoint) { + // If the type of the moved instruction is throwing and we don't have a position at the + // insertion point, we use the special synthetic-none position, which is OK as the moved + // instruction instance is known not to throw (or we would not be allowed the move it). + if (movedInstruction.instructionTypeCanThrow() && !insertionPoint.getPosition().isSome()) { + return Position.syntheticNone(); + } + return insertionPoint.getPosition(); + } + + private void shortenLiveRangesInsideBlock( + IRCode code, + BasicBlock block, + LazyBox dominatorTreeMemoization, + Map> addConstantInBlock, + Predicate selector) { + InstructionListIterator iterator = block.listIterator(code); + boolean seenCompareExit = false; + while (iterator.hasNext()) { + Instruction instruction = iterator.next(); + if (options.canHaveCmpIfFloatBug() && instruction.isCmp()) { + seenCompareExit = true; + } + + if (instruction.hasUnusedOutValue() || instruction.outValue().hasLocalInfo()) { + continue; + } + + if (!selector.test(instruction)) { + continue; + } + + // Here we try to stop wasting time in the common case where constants are used immediately + // after their definition. + // + // This is especially important for the creation of large arrays, which has the following code + // pattern repeated many times, where the two loaded constants are only used by the ArrayPut + // instruction. + // + // Const number (the array index) + // Const (the array entry value) + // ArrayPut + // + // The heuristic is therefore to check for constants used only once if the use is within the + // next two instructions, and only swap them if that is the case (cannot shorten the live + // range anyway). + if (instruction.outValue().hasSingleUniqueUser() && !instruction.outValue().hasPhiUsers()) { + Instruction uniqueUse = instruction.outValue().singleUniqueUser(); + Instruction next = iterator.next(); + if (uniqueUse == next) { + iterator.previous(); + continue; + } + if (next.hasOutValue() + && next.outValue().hasSingleUniqueUser() + && !next.outValue().hasPhiUsers() + && iterator.hasNext()) { + Instruction nextNext = iterator.peekNext(); + Instruction uniqueUseNext = next.outValue().singleUniqueUser(); + if (uniqueUse == nextNext && uniqueUseNext == nextNext) { + iterator.previous(); + continue; + } + } + iterator.previous(); + // The call to removeOrReplaceByDebugLocalRead() at the end of this method will remove the + // last returned element of this iterator. Therefore, we re-read this element from the + // iterator. + iterator.previous(); + iterator.next(); + } + // Collect the blocks for all users of the constant. + Set userBlocks = new LinkedHashSet<>(); + for (Instruction user : instruction.outValue().uniqueUsers()) { + userBlocks.add(user.getBlock()); + } + for (Phi phi : instruction.outValue().uniquePhiUsers()) { + int predecessorIndex = 0; + for (Value operand : phi.getOperands()) { + if (operand == instruction.outValue()) { + userBlocks.add(phi.getBlock().getPredecessors().get(predecessorIndex)); + } + predecessorIndex++; + } + } + // Locate the closest dominator block for all user blocks. + DominatorTree dominatorTree = dominatorTreeMemoization.computeIfAbsent(); + BasicBlock dominator = dominatorTree.closestDominator(userBlocks); + + if (instruction.instructionTypeCanThrow()) { + if (block.hasCatchHandlers() || dominator.hasCatchHandlers()) { + // Do not move the constant if the constant instruction can throw + // and the dominator or the original block has catch handlers. + continue; + } + } + + // If the dominator block has a potential compare exit we will chose that as the insertion + // point. Uniquely for instructions having invalues this can be before the definition of them. + // Bail-out when this is the case. See b/251015885 for more information. + if (seenCompareExit + && Iterables.any(instruction.inValues(), x -> x.getBlock() == dominator)) { + continue; + } + + Instruction copy; + switch (instruction.opcode()) { + case CONST_CLASS: + copy = ConstClass.copyOf(code, instruction.asConstClass()); + break; + case CONST_NUMBER: + copy = ConstNumber.copyOf(code, instruction.asConstNumber()); + break; + case CONST_STRING: + copy = ConstString.copyOf(code, instruction.asConstString()); + break; + case DEX_ITEM_BASED_CONST_STRING: + copy = DexItemBasedConstString.copyOf(code, instruction.asDexItemBasedConstString()); + break; + case INSTANCE_GET: + copy = InstanceGet.copyOf(code, instruction.asInstanceGet()); + break; + case STATIC_GET: + copy = StaticGet.copyOf(code, instruction.asStaticGet()); + break; + default: + throw new Unreachable(); + } + instruction.outValue().replaceUsers(copy.outValue()); + addConstantInBlock + .computeIfAbsent(dominator, k -> new LinkedHashMap<>()) + .put(copy.outValue(), copy); + assert iterator.peekPrevious() == instruction; + iterator.removeOrReplaceByDebugLocalRead(); + } + } +} 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 3a60f68b1d..af0023f357 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 @@ -6,12 +6,6 @@ import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; -import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS; -import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER; -import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING; -import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING; -import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; -import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; @@ -31,23 +25,17 @@ import com.android.tools.r8.ir.code.ArrayLength; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.BasicBlockIterator; -import com.android.tools.r8.ir.code.Binop; -import com.android.tools.r8.ir.code.ConstClass; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; import com.android.tools.r8.ir.code.DebugLocalWrite; import com.android.tools.r8.ir.code.DebugLocalsChange; -import com.android.tools.r8.ir.code.DexItemBasedConstString; import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; -import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; -import com.android.tools.r8.ir.code.InstructionOrPhi; import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeInterface; import com.android.tools.r8.ir.code.InvokeMethod; @@ -63,7 +51,6 @@ import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.collect.Streams; import it.unimi.dsi.fastutil.ints.Int2IntMap; @@ -75,25 +62,16 @@ import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -import it.unimi.dsi.fastutil.objects.Reference2IntMap; -import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; -import java.util.function.Predicate; public class CodeRewriter { - // This constant was determined by experimentation. - private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50; - private final AppView appView; private final DexItemFactory dexItemFactory; private final InternalOptions options; @@ -366,422 +344,9 @@ public void rewriteKnownArrayLengthCalls(IRCode code) { assert code.isConsistentSSA(appView); } - /** - * If an instruction is known to be a binop/lit8 or binop//lit16 instruction, update the - * instruction to use its own constant that will be defined just before the instruction. This - * transformation allows to decrease pressure on register allocation by defining the shortest - * range of constant used by this kind of instruction. D8 knowns at build time that constant will - * be encoded directly into the final Dex instruction. - */ - public void useDedicatedConstantForLitInstruction(IRCode code) { - if (!code.metadata().mayHaveArithmeticOrLogicalBinop()) { - return; - } - for (BasicBlock block : code.blocks) { - InstructionListIterator instructionIterator = block.listIterator(code); - // Collect all the non constant in values for binop/lit8 or binop/lit16 instructions. - Set binopsWithLit8OrLit16NonConstantValues = Sets.newIdentityHashSet(); - while (instructionIterator.hasNext()) { - Instruction currentInstruction = instructionIterator.next(); - if (!isBinopWithLit8OrLit16(currentInstruction)) { - continue; - } - Value value = binopWithLit8OrLit16NonConstant(currentInstruction.asBinop()); - assert value != null; - binopsWithLit8OrLit16NonConstantValues.add(value); - } - if (binopsWithLit8OrLit16NonConstantValues.isEmpty()) { - continue; - } - // Find last use in block of all the non constant in values for binop/lit8 or binop/lit16 - // instructions. - Reference2IntMap lastUseOfBinopsWithLit8OrLit16NonConstantValues = - new Reference2IntOpenHashMap<>(); - lastUseOfBinopsWithLit8OrLit16NonConstantValues.defaultReturnValue(-1); - int currentInstructionNumber = block.getInstructions().size(); - while (instructionIterator.hasPrevious()) { - Instruction currentInstruction = instructionIterator.previous(); - currentInstructionNumber--; - for (Value value : - Iterables.concat(currentInstruction.inValues(), currentInstruction.getDebugValues())) { - if (!binopsWithLit8OrLit16NonConstantValues.contains(value)) { - continue; - } - if (!lastUseOfBinopsWithLit8OrLit16NonConstantValues.containsKey(value)) { - lastUseOfBinopsWithLit8OrLit16NonConstantValues.put(value, currentInstructionNumber); - } - } - } - // Do the transformation except if the binop can use the binop/2addr format. - currentInstructionNumber--; - assert currentInstructionNumber == -1; - while (instructionIterator.hasNext()) { - Instruction currentInstruction = instructionIterator.next(); - currentInstructionNumber++; - if (!isBinopWithLit8OrLit16(currentInstruction)) { - continue; - } - Binop binop = currentInstruction.asBinop(); - if (!canBe2AddrInstruction( - binop, currentInstructionNumber, lastUseOfBinopsWithLit8OrLit16NonConstantValues)) { - Value constValue = binopWithLit8OrLit16Constant(currentInstruction); - if (constValue.numberOfAllUsers() > 1) { - // No need to do the transformation if the const value is already used only one time. - ConstNumber newConstant = ConstNumber - .copyOf(code, constValue.definition.asConstNumber()); - newConstant.setPosition(currentInstruction.getPosition()); - newConstant.setBlock(currentInstruction.getBlock()); - currentInstruction.replaceValue(constValue, newConstant.outValue()); - constValue.removeUser(currentInstruction); - instructionIterator.previous(); - instructionIterator.add(newConstant); - instructionIterator.next(); - } - } - } - } - - assert code.isConsistentSSA(appView); - } - // Check if a binop can be represented in the binop/lit8 or binop/lit16 form. - private static boolean isBinopWithLit8OrLit16(Instruction instruction) { - if (!instruction.isArithmeticBinop() && !instruction.isLogicalBinop()) { - return false; - } - Binop binop = instruction.asBinop(); - // If one of the values does not need a register it is implicitly a binop/lit8 or binop/lit16. - boolean result = - !binop.needsValueInRegister(binop.leftValue()) - || !binop.needsValueInRegister(binop.rightValue()); - assert !result || binop.leftValue().isConstNumber() || binop.rightValue().isConstNumber(); - return result; - } - - // Return the constant in-value of a binop/lit8 or binop/lit16 instruction. - private static Value binopWithLit8OrLit16Constant(Instruction instruction) { - assert isBinopWithLit8OrLit16(instruction); - Binop binop = instruction.asBinop(); - if (binop.leftValue().isConstNumber()) { - return binop.leftValue(); - } else if (binop.rightValue().isConstNumber()) { - return binop.rightValue(); - } else { - throw new Unreachable(); - } - } - - // Return the non-constant in-value of a binop/lit8 or binop/lit16 instruction. - private static Value binopWithLit8OrLit16NonConstant(Binop binop) { - if (binop.leftValue().isConstNumber()) { - return binop.rightValue(); - } else if (binop.rightValue().isConstNumber()) { - return binop.leftValue(); - } else { - throw new Unreachable(); - } - } - - /** - * Estimate if a binary operation can be a binop/2addr form or not. It can be a 2addr form when an - * argument is no longer needed after the binary operation and can be overwritten. That is - * definitely the case if there is no path between the binary operation and all other usages. - */ - private static boolean canBe2AddrInstruction( - Binop binop, int binopInstructionNumber, Reference2IntMap lastUseOfRelevantValue) { - Value value = binopWithLit8OrLit16NonConstant(binop); - assert value != null; - int lastUseInstructionNumber = lastUseOfRelevantValue.getInt(value); - // The binop instruction is a user, so there is always a last use in the block. - assert lastUseInstructionNumber != -1; - if (lastUseInstructionNumber > binopInstructionNumber) { - return false; - } - - Set noPathTo = Sets.newIdentityHashSet(); - BasicBlock binopBlock = binop.getBlock(); - Iterable users = - value.debugUsers() != null - ? Iterables.concat(value.uniqueUsers(), value.debugUsers(), value.uniquePhiUsers()) - : Iterables.concat(value.uniqueUsers(), value.uniquePhiUsers()); - for (InstructionOrPhi user : users) { - BasicBlock userBlock = user.getBlock(); - if (userBlock == binopBlock) { - // All users in the current block are either before the binop instruction or the - // binop instruction itself. - continue; - } - if (noPathTo.contains(userBlock)) { - continue; - } - if (binopBlock.hasPathTo(userBlock)) { - return false; - } - noPathTo.add(userBlock); - } - - return true; - } - - public void shortenLiveRanges(IRCode code, ConstantCanonicalizer canonicalizer) { - if (options.testing.disableShortenLiveRanges) { - return; - } - LazyBox dominatorTreeMemoization = new LazyBox<>(() -> new DominatorTree(code)); - Map> addConstantInBlock = new IdentityHashMap<>(); - LinkedList blocks = code.blocks; - for (BasicBlock block : blocks) { - shortenLiveRangesInsideBlock( - code, - block, - dominatorTreeMemoization, - addConstantInBlock, - canonicalizer::isConstantCanonicalizationCandidate); - } - - // Heuristic to decide if constant instructions are shared in dominator block - // of usages or moved to the usages. - - // Process all blocks in stable order to avoid non-determinism of hash map iterator. - BasicBlockIterator blockIterator = code.listIterator(); - while (blockIterator.hasNext()) { - BasicBlock block = blockIterator.next(); - Map movedInstructions = addConstantInBlock.get(block); - if (movedInstructions == null) { - continue; - } - assert !movedInstructions.isEmpty(); - if (!block.isEntry() && movedInstructions.size() > STOP_SHARED_CONSTANT_THRESHOLD) { - // If there are too many constant numbers in the same block they are copied rather than - // shared unless used by a phi. - movedInstructions - .values() - .removeIf( - movedInstruction -> { - if (movedInstruction.outValue().hasPhiUsers() - || !movedInstruction.isConstNumber()) { - return false; - } - ConstNumber constNumber = movedInstruction.asConstNumber(); - Value constantValue = movedInstruction.outValue(); - for (Instruction user : constantValue.uniqueUsers()) { - ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber); - newCstNum.setPosition(user.getPosition()); - InstructionListIterator iterator = user.getBlock().listIterator(code, user); - iterator.previous(); - iterator.add(newCstNum); - user.replaceValue(constantValue, newCstNum.outValue()); - } - constantValue.clearUsers(); - return true; - }); - } - // Add constant into the dominator block of usages. - boolean hasCatchHandlers = block.hasCatchHandlers(); - InstructionListIterator instructionIterator = block.listIterator(code); - while (instructionIterator.hasNext()) { - Instruction insertionPoint = instructionIterator.next(); - if (insertionPoint.isJumpInstruction() - || (hasCatchHandlers && insertionPoint.instructionTypeCanThrow()) - || (options.canHaveCmpIfFloatBug() && insertionPoint.isCmp())) { - break; - } - for (Value use : - Iterables.concat(insertionPoint.inValues(), insertionPoint.getDebugValues())) { - Instruction movedInstruction = movedInstructions.remove(use); - if (movedInstruction != null) { - instructionIterator = - insertInstructionWithShortenedLiveRange( - code, blockIterator, instructionIterator, movedInstruction, insertionPoint); - } - } - } - - // Insert remaining constant instructions prior to the "exit". - Instruction insertionPoint = instructionIterator.peekPrevious(); - for (Instruction movedInstruction : movedInstructions.values()) { - instructionIterator = - insertInstructionWithShortenedLiveRange( - code, blockIterator, instructionIterator, movedInstruction, insertionPoint); - } - } - - assert code.isConsistentSSA(appView); - } - - private InstructionListIterator insertInstructionWithShortenedLiveRange( - IRCode code, - BasicBlockIterator blockIterator, - InstructionListIterator instructionIterator, - Instruction movedInstruction, - Instruction insertionPoint) { - Instruction previous = instructionIterator.previous(); - assert previous == insertionPoint; - movedInstruction.setPosition( - getPositionForMovedNonThrowingInstruction(movedInstruction, insertionPoint)); - if (movedInstruction.instructionTypeCanThrow() - && insertionPoint.getBlock().hasCatchHandlers()) { - // Split the block and reset the block iterator. - BasicBlock splitBlock = - instructionIterator.splitCopyCatchHandlers(code, blockIterator, appView.options()); - BasicBlock previousBlock = blockIterator.previousUntil(b -> b == splitBlock); - assert previousBlock == splitBlock; - blockIterator.next(); - - // Add the constant instruction before the exit instruction. - assert !instructionIterator.hasNext(); - instructionIterator.previous(); - instructionIterator.add(movedInstruction); - - // Continue insertion at the entry of the split block. - instructionIterator = splitBlock.listIterator(code); - } else { - instructionIterator.add(movedInstruction); - } - Instruction next = instructionIterator.next(); - assert next == insertionPoint; - return instructionIterator; - } - - private Position getPositionForMovedNonThrowingInstruction( - Instruction movedInstruction, Instruction insertionPoint) { - // If the type of the moved instruction is throwing and we don't have a position at the - // insertion point, we use the special synthetic-none position, which is OK as the moved - // instruction instance is known not to throw (or we would not be allowed the move it). - if (movedInstruction.instructionTypeCanThrow() && !insertionPoint.getPosition().isSome()) { - return Position.syntheticNone(); - } - return insertionPoint.getPosition(); - } - - private void shortenLiveRangesInsideBlock( - IRCode code, - BasicBlock block, - LazyBox dominatorTreeMemoization, - Map> addConstantInBlock, - Predicate selector) { - InstructionListIterator iterator = block.listIterator(code); - boolean seenCompareExit = false; - while (iterator.hasNext()) { - Instruction instruction = iterator.next(); - if (options.canHaveCmpIfFloatBug() && instruction.isCmp()) { - seenCompareExit = true; - } - - if (instruction.hasUnusedOutValue() || instruction.outValue().hasLocalInfo()) { - continue; - } - - if (!selector.test(instruction)) { - continue; - } - - // Here we try to stop wasting time in the common case where constants are used immediately - // after their definition. - // - // This is especially important for the creation of large arrays, which has the following code - // pattern repeated many times, where the two loaded constants are only used by the ArrayPut - // instruction. - // - // Const number (the array index) - // Const (the array entry value) - // ArrayPut - // - // The heuristic is therefore to check for constants used only once if the use is within the - // next two instructions, and only swap them if that is the case (cannot shorten the live - // range anyway). - if (instruction.outValue().hasSingleUniqueUser() && !instruction.outValue().hasPhiUsers()) { - Instruction uniqueUse = instruction.outValue().singleUniqueUser(); - Instruction next = iterator.next(); - if (uniqueUse == next) { - iterator.previous(); - continue; - } - if (next.hasOutValue() - && next.outValue().hasSingleUniqueUser() - && !next.outValue().hasPhiUsers() - && iterator.hasNext()) { - Instruction nextNext = iterator.peekNext(); - Instruction uniqueUseNext = next.outValue().singleUniqueUser(); - if (uniqueUse == nextNext && uniqueUseNext == nextNext) { - iterator.previous(); - continue; - } - } - iterator.previous(); - // The call to removeOrReplaceByDebugLocalRead() at the end of this method will remove the - // last returned element of this iterator. Therefore, we re-read this element from the - // iterator. - iterator.previous(); - iterator.next(); - } - // Collect the blocks for all users of the constant. - Set userBlocks = new LinkedHashSet<>(); - for (Instruction user : instruction.outValue().uniqueUsers()) { - userBlocks.add(user.getBlock()); - } - for (Phi phi : instruction.outValue().uniquePhiUsers()) { - int predecessorIndex = 0; - for (Value operand : phi.getOperands()) { - if (operand == instruction.outValue()) { - userBlocks.add(phi.getBlock().getPredecessors().get(predecessorIndex)); - } - predecessorIndex++; - } - } - // Locate the closest dominator block for all user blocks. - DominatorTree dominatorTree = dominatorTreeMemoization.computeIfAbsent(); - BasicBlock dominator = dominatorTree.closestDominator(userBlocks); - - if (instruction.instructionTypeCanThrow()) { - if (block.hasCatchHandlers() || dominator.hasCatchHandlers()) { - // Do not move the constant if the constant instruction can throw - // and the dominator or the original block has catch handlers. - continue; - } - } - - // If the dominator block has a potential compare exit we will chose that as the insertion - // point. Uniquely for instructions having invalues this can be before the definition of them. - // Bail-out when this is the case. See b/251015885 for more information. - if (seenCompareExit - && Iterables.any(instruction.inValues(), x -> x.getBlock() == dominator)) { - continue; - } - - Instruction copy; - switch (instruction.opcode()) { - case CONST_CLASS: - copy = ConstClass.copyOf(code, instruction.asConstClass()); - break; - case CONST_NUMBER: - copy = ConstNumber.copyOf(code, instruction.asConstNumber()); - break; - case CONST_STRING: - copy = ConstString.copyOf(code, instruction.asConstString()); - break; - case DEX_ITEM_BASED_CONST_STRING: - copy = DexItemBasedConstString.copyOf(code, instruction.asDexItemBasedConstString()); - break; - case INSTANCE_GET: - copy = InstanceGet.copyOf(code, instruction.asInstanceGet()); - break; - case STATIC_GET: - copy = StaticGet.copyOf(code, instruction.asStaticGet()); - break; - default: - throw new Unreachable(); - } - instruction.outValue().replaceUsers(copy.outValue()); - addConstantInBlock - .computeIfAbsent(dominator, k -> new LinkedHashMap<>()) - .put(copy.outValue(), copy); - assert iterator.peekPrevious() == instruction; - iterator.removeOrReplaceByDebugLocalRead(); - } - } // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block. private static boolean hasLocalOrLineChangeBetween( From c15a7f747085c0835e2a456ff1ed581cd40f3ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gjesse?= Date: Fri, 9 Jun 2023 12:29:16 +0200 Subject: [PATCH 072/153] Don't include keep annotations jar when releasing to udc-dev The udc-dev branch was branched from 8.2.2-dev where the keep annotation jar was not built. Change-Id: Icb52333f259f896c5c02e7c0f448aa29e92bb0fd --- tools/r8_release.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/r8_release.py b/tools/r8_release.py index dc47fa2d78..3813dbe87d 100755 --- a/tools/r8_release.py +++ b/tools/r8_release.py @@ -220,8 +220,11 @@ def release_aosp(options): Test: TARGET_PRODUCT=aosp_arm64 m -j core-oj""" % (args.version, args.version, args.version)) + # Fixes to Android U branch is based of 8.2.2-dev where the keepanno library + # is not built. + keepanno = not args.version.startswith('8.2.2-udc') return release_studio_or_aosp( - utils.REPO_ROOT, args.aosp, options, git_message, keepanno=True) + utils.REPO_ROOT, args.aosp, options, git_message, keepanno=keepanno) return release_aosp From 5c4b713364972d12b62ee7961fcdf7db10f278f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Mon, 12 Jun 2023 10:34:17 +0200 Subject: [PATCH 073/153] Fix L8 determinism - Wrapper generation trigger side conversion generation for arrays which should be generated in deterministic order Bug: b/286500678 Change-Id: I45f8a5fa66e90baf8223d0665c5d0527f5eb3166 --- .../apiconversion/DesugaredLibraryWrapperSynthesizer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java index cc5d7d6726..b950826c53 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java @@ -46,8 +46,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.BiFunction; @@ -732,7 +732,7 @@ public void synthesizeClasses( CfClassSynthesizerDesugaringEventConsumer eventConsumer) { MachineDesugaredLibrarySpecification librarySpecification = appView.options().machineDesugaredLibrarySpecification; - Map> validClassesToWrap = new IdentityHashMap<>(); + Map> validClassesToWrap = new LinkedHashMap<>(); librarySpecification .getWrappers() .forEach( From 841eee39ccba17432f48ff4e5960e8f0d6ff95cb Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Mon, 12 Jun 2023 10:47:03 +0200 Subject: [PATCH 074/153] Add art command cache This will store the execution output (exitcode, stdout, stderr) in local files and return these if the inputs and all arguments are the same (sha256ing both input files and arguments) This is currently under a flag tools/test.py --art_cache_dir /tmp/artcache will utilize the cache Bug: 286019067 Change-Id: I40f18186389516da11743f61d88401ac2c48e289 --- build.gradle | 3 + .../java/com/android/tools/r8/ToolHelper.java | 173 ++++++++++++++++++ tools/test.py | 6 + 3 files changed, 182 insertions(+) diff --git a/build.gradle b/build.gradle index 3c67f45726..3fef251aa2 100644 --- a/build.gradle +++ b/build.gradle @@ -2254,6 +2254,9 @@ test { task -> if (project.hasProperty('test_dir')) { systemProperty 'test_dir', project.property('test_dir') } + if (project.hasProperty('art_cache_dir')) { + systemProperty 'art_cache_dir', project.property('art_cache_dir') + } if (project.hasProperty('r8lib')) { dependsOn configureTestForR8Lib // R8lib should be used instead of the main output and all the tests in diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java index 655350b90a..46bdb5dc69 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java @@ -43,10 +43,13 @@ import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.ZipUtils; +import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; import com.google.gson.Gson; @@ -64,6 +67,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; @@ -535,6 +539,7 @@ public static class ArtCommandBuilder extends CommandBuilder { private DexVm version; private boolean withArtFrameworks; + private ArtResultCacheLookupKey artResultCacheLookupKey; public ArtCommandBuilder() { this.version = getDexVm(); @@ -582,6 +587,169 @@ public DeviceRunner asDeviceRunner() { .setMainClass(mainClass) .setProgramArguments(programArguments); } + + private boolean useCache() { + return CommandResultCache.getInstance() != null; + } + + public void cacheResult(ProcessResult result) { + if (useCache()) { + assert artResultCacheLookupKey != null; + CommandResultCache.getInstance().putResult(result, artResultCacheLookupKey); + } + } + + public ProcessResult getCachedResults() { + if (!useCache()) { + return null; + } + assert artResultCacheLookupKey == null; + // Reuse the key when storing results if this is not already cached. + artResultCacheLookupKey = new ArtResultCacheLookupKey(this::hashParts); + return CommandResultCache.getInstance().lookup(artResultCacheLookupKey); + } + + private void hashParts(Hasher hasher) { + // Call getExecutable first, this will set executionDirectory if needed. + hasher.putString(this.getExecutable(), StandardCharsets.UTF_8); + if (this.executionDirectory != null) { + hasher.putString(this.executionDirectory, StandardCharsets.UTF_8); + } + hasher.putString(this.mainClass, StandardCharsets.UTF_8); + hasher.putBoolean(this.withArtFrameworks); + hashFilesFromList(hasher, classpaths); + hashFilesFromList(hasher, bootClasspaths); + systemProperties.forEach( + (s, t) -> + hasher.putString(s, StandardCharsets.UTF_8).putString(t, StandardCharsets.UTF_8)); + programArguments.forEach(s -> hasher.putString(s, StandardCharsets.UTF_8)); + options.forEach(s -> hasher.putString(s, StandardCharsets.UTF_8)); + } + + private static void hashFilesFromList(Hasher hasher, List files) { + for (String file : files) { + try { + hasher.putBytes(Files.readAllBytes(Paths.get(file))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + + private static class ArtResultCacheLookupKey { + private final Consumer hasherConsumer; + private String hash; + + public ArtResultCacheLookupKey(Consumer hasherConsumer) { + this.hasherConsumer = hasherConsumer; + } + + public String getHash() { + if (hash == null) { + Hasher hasher = Hashing.sha256().newHasher(); + hasherConsumer.accept(hasher); + hash = hasher.hash().toString(); + } + return hash; + } + } + + private static class CommandResultCache { + private static CommandResultCache INSTANCE = + System.getProperty("art_cache_dir") != null + ? new CommandResultCache(Paths.get(System.getProperty("art_cache_dir"))) + : null; + private final Path path; + + public CommandResultCache(Path path) { + this.path = path; + } + + public static CommandResultCache getInstance() { + return INSTANCE; + } + + private Path getStdoutFile(ArtResultCacheLookupKey artResultCacheLookupKey) { + return path.resolve(artResultCacheLookupKey.getHash() + ".stdout"); + } + + private Path getStderrFile(ArtResultCacheLookupKey artResultCacheLookupKey) { + return path.resolve(artResultCacheLookupKey.getHash() + ".stderr"); + } + + private Path getExitCodeFile(ArtResultCacheLookupKey artResultCacheLookupKey) { + return path.resolve(artResultCacheLookupKey.getHash()); + } + + private Path getTempFile(Path path) { + return Paths.get(path.toString() + ".temp" + Thread.currentThread().getId()); + } + + private String getStringContent(Path path) { + assert path.toFile().exists(); + if (path.toFile().length() > 0) { + try { + return FileUtils.readTextFile(path, Charsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return ""; + } + + public ProcessResult lookup(ArtResultCacheLookupKey artResultCacheLookupKey) { + // TODO Add concurrency handling! + Path exitCodeFile = getExitCodeFile(artResultCacheLookupKey); + if (exitCodeFile.toFile().exists()) { + int exitCode = Integer.parseInt(getStringContent(exitCodeFile)); + // Because of the temp files and order of writing we should never get here with an + // inconsistent state. It is possible, although unlikely, that the stdout/stderr + // (and even exitcode if art is non deterministic) are from different, process ids etc, + // but this should have no impact. + return new ProcessResult( + exitCode, + getStringContent(getStdoutFile(artResultCacheLookupKey)), + getStringContent(getStderrFile(artResultCacheLookupKey))); + } + return null; + } + + public void putResult(ProcessResult result, ArtResultCacheLookupKey artResultCacheLookupKey) { + try { + String exitCode = "" + result.exitCode; + // We avoid race conditions of writing vs reading by first writing all 3 files to temp + // files, then moving these to the result files, moving last the exitcode file (which is + // what we use as cache present check) + Path exitCodeFile = getExitCodeFile(artResultCacheLookupKey); + Path exitCodeTempFile = getTempFile(exitCodeFile); + Path stdoutFile = getStdoutFile(artResultCacheLookupKey); + Path stdoutTempFile = getTempFile(stdoutFile); + Path stderrFile = getStderrFile(artResultCacheLookupKey); + Path stderrTempFile = getTempFile(stderrFile); + Files.write(exitCodeTempFile, exitCode.getBytes(StandardCharsets.UTF_8)); + Files.write(stdoutTempFile, result.stdout.getBytes(StandardCharsets.UTF_8)); + Files.write(stderrTempFile, result.stderr.getBytes(StandardCharsets.UTF_8)); + // Order is important, move exitcode file last! + Files.move( + stdoutTempFile, + stdoutFile, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + Files.move( + stderrTempFile, + stderrFile, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + Files.move( + exitCodeTempFile, + exitCodeFile, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } private static List toFileList(List filePathList) { @@ -1910,6 +2078,10 @@ protected static void failOnVerificationErrors(ProcessResult result) { private static ProcessResult runArtProcessRaw(ArtCommandBuilder builder) throws IOException { Assume.assumeTrue(artSupported() || dealsWithGoldenFiles()); + ProcessResult cachedResult = builder.getCachedResults(); + if (cachedResult != null) { + return cachedResult; + } ProcessResult result; if (builder.isForDevice()) { try { @@ -1920,6 +2092,7 @@ private static ProcessResult runArtProcessRaw(ArtCommandBuilder builder) throws } else { result = runProcess(builder.asProcessBuilder()); } + builder.cacheResult(result); return result; } diff --git a/tools/test.py b/tools/test.py index a8e279f25d..6060e041f0 100755 --- a/tools/test.py +++ b/tools/test.py @@ -113,6 +113,8 @@ def ParseOptions(): help='Use a custom directory for the test artifacts instead of a' ' temporary (which is automatically removed after the test).' ' Note that the directory will not be cleared before the test.') + result.add_option('--art-cache-dir', '--art_cache_dir', + help='Cache art invocations to this directory, speeds up test runs') result.add_option('--java-home', '--java_home', help='Use a custom java version to run tests.') result.add_option('--java-max-memory-size', '--java_max_memory_size', @@ -316,6 +318,10 @@ def Main(): gradle_args.append('-Ptest_dir=' + options.test_dir) if not os.path.exists(options.test_dir): os.makedirs(options.test_dir) + if options.art_cache_dir: + gradle_args.append('-Part_cache_dir=' + options.art_cache_dir) + if not os.path.exists(options.art_cache_dir): + os.makedirs(options.art_cache_dir) if options.java_home: gradle_args.append('-Dorg.gradle.java.home=' + options.java_home) if options.java_max_memory_size: From 11213a84770a1b54320033d101be951bfd5c126a Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Mon, 12 Jun 2023 11:37:54 +0000 Subject: [PATCH 075/153] Revert "Add a check release column to check for missing cherry-picks" Bug: b/286177952 This reverts commit 0252f5d2269bda272e573837da5b12fba493dc84. Reason for revert: should run main script content Change-Id: Ib11515296a6a13115129af3c1b07061a8bc61bf3 --- .../global/generated/cr-buildbucket.cfg | 29 ------------------- infra/config/global/generated/luci-milo.cfg | 5 ---- infra/config/global/generated/luci-notify.cfg | 12 -------- .../global/generated/luci-scheduler.cfg | 16 ---------- infra/config/global/generated/project.cfg | 2 +- infra/config/global/main.star | 18 ------------ 6 files changed, 1 insertion(+), 81 deletions(-) diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg index 7c515d5251..aa285e07e0 100644 --- a/infra/config/global/generated/cr-buildbucket.cfg +++ b/infra/config/global/generated/cr-buildbucket.cfg @@ -76,35 +76,6 @@ buckets { value: 100 } } - builders { - name: "check_release" - swarming_host: "chrome-swarming.appspot.com" - swarming_tags: "vpython:native-python-wrapper" - dimensions: "cores:8" - dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-20.04" - dimensions: "pool:luci.r8.ci" - exe { - cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" - cipd_version: "refs/heads/master" - cmd: "luciexe" - } - properties: - '{' - ' "builder_group": "internal.client.r8",' - ' "recipe": "rex",' - ' "test_wrapper": "tools/check-cherry-picks.py"' - '}' - priority: 25 - execution_timeout_secs: 1800 - expiration_secs: 126000 - build_numbers: YES - service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com" - experiments { - key: "luci.recipes.use_python3" - value: 100 - } - } builders { name: "desugared_library-head" swarming_host: "chrome-swarming.appspot.com" diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg index 6020a0605b..b1481fe595 100644 --- a/infra/config/global/generated/luci-milo.cfg +++ b/infra/config/global/generated/luci-milo.cfg @@ -155,11 +155,6 @@ consoles { category: "Release|archive" short_name: "archive" } - builders { - name: "buildbucket/luci.r8.ci/check_release" - category: "Release|archive" - short_name: "check" - } builders { name: "buildbucket/luci.r8.ci/linux-dex_default_release" category: "Release|R8" diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg index 406f9e48e6..fa8d5b0aab 100644 --- a/infra/config/global/generated/luci-notify.cfg +++ b/infra/config/global/generated/luci-notify.cfg @@ -28,18 +28,6 @@ notifiers { repository: "https://r8.googlesource.com/r8" } } -notifiers { - notifications { - on_failure: true - on_new_failure: true - notify_blamelist {} - } - builders { - bucket: "ci" - name: "check_release" - repository: "https://r8.googlesource.com/r8" - } -} notifiers { notifications { on_failure: true diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg index cc010ab644..e59d20c1e8 100644 --- a/infra/config/global/generated/luci-scheduler.cfg +++ b/infra/config/global/generated/luci-scheduler.cfg @@ -34,21 +34,6 @@ job { builder: "archive_release" } } -job { - id: "check_release" - realm: "ci" - acl_sets: "ci" - triggering_policy { - kind: GREEDY_BATCHING - max_concurrent_invocations: 3 - max_batch_size: 1 - } - buildbucket { - server: "cr-buildbucket.appspot.com" - bucket: "ci" - builder: "check_release" - } -} job { id: "desugared_library-head" realm: "ci" @@ -785,7 +770,6 @@ trigger { realm: "ci" acl_sets: "ci" triggers: "archive_release" - triggers: "check_release" triggers: "linux-android-10.0.0_release" triggers: "linux-android-4.0.4_release" triggers: "linux-android-4.4.4_release" diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg index ee2ef9d548..cfee79c7e6 100644 --- a/infra/config/global/generated/project.cfg +++ b/infra/config/global/generated/project.cfg @@ -7,7 +7,7 @@ name: "r8" access: "group:all" lucicfg { - version: "1.39.8" + version: "1.39.4" package_dir: ".." config_dir: "generated" entry_point: "main.star" diff --git a/infra/config/global/main.star b/infra/config/global/main.star index 74c5548e61..d9201a8de5 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star @@ -283,24 +283,6 @@ def archivers(): ) archivers() -r8_builder( - "check_release", - category = "archive", - dimensions = get_dimensions(), - triggering_policy = scheduler.policy( - kind = scheduler.GREEDY_BATCHING_KIND, - max_batch_size = 1, - max_concurrent_invocations = 3 - ), - priority = 25, - properties = { - "test_wrapper" : "tools/check-cherry-picks.py", - "builder_group" : "internal.client.r8" - }, - execution_timeout = time.minute * 30, - expiration_timeout = time.hour * 35, -) - r8_tester_with_default("linux-dex_default", ["--runtimes=dex-default"]) r8_tester_with_default("linux-none", ["--runtimes=none"]) r8_tester_with_default("linux-jdk8", ["--runtimes=jdk8"]) From 8d4c33a7bdb4611d7a05477ebe7dc8d5e12c8ecd Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Mon, 12 Jun 2023 13:50:38 +0200 Subject: [PATCH 076/153] Add timing to AppView#pruneItems Change-Id: Ibf938573acb3f2a11d1c2623ba0cbd0e935599f7 --- src/main/java/com/android/tools/r8/R8.java | 7 ++--- .../com/android/tools/r8/graph/AppInfo.java | 14 ++++++--- .../r8/graph/AppInfoWithClassHierarchy.java | 18 +++++++---- .../android/tools/r8/graph/AppServices.java | 7 +++-- .../com/android/tools/r8/graph/AppView.java | 31 ++++++++++--------- .../HorizontalClassMerger.java | 2 +- .../ir/conversion/PrimaryR8IRConverter.java | 3 +- ...DefaultOpenClosedInterfacesCollection.java | 2 +- ...onEmptyOpenClosedInterfacesCollection.java | 8 +++-- .../OpenClosedInterfacesCollection.java | 3 +- .../RedundantBridgeRemover.java | 17 +++++++--- .../r8/profile/art/ArtProfileCollection.java | 2 +- .../art/EmptyArtProfileCollection.java | 2 +- .../art/NonEmptyArtProfileCollection.java | 8 +++-- .../startup/profile/EmptyStartupProfile.java | 2 +- .../profile/NonEmptyStartupProfile.java | 29 ++++++++++------- .../startup/profile/StartupProfile.java | 2 +- .../tools/r8/shaking/AppInfoWithLiveness.java | 5 ++- .../r8/shaking/AssumeInfoCollection.java | 7 +++-- .../shaking/ProguardCompatibilityActions.java | 7 +++-- .../tools/r8/shaking/RootSetUtils.java | 17 +++++----- .../android/tools/r8/shaking/TreePruner.java | 26 +++++++--------- .../r8/synthesis/SyntheticFinalization.java | 6 ++-- 23 files changed, 137 insertions(+), 88 deletions(-) diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index f345f1b46c..7bd87671f6 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -413,7 +413,6 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO .notifyTreePrunerFinished(Enqueuer.Mode.INITIAL_TREE_SHAKING); // Recompute the subtyping information. - appView.pruneItems(prunedItems, executorService); new AbstractMethodRemover( appViewWithLiveness, appViewWithLiveness.appInfo().computeSubtypingInfo()) .run(); @@ -454,7 +453,7 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO // We can now remove redundant bridges. Note that we do not need to update the // invoke-targets here, as the existing invokes will simply dispatch to the now // visible super-method. MemberRebinding, if run, will then dispatch it correctly. - new RedundantBridgeRemover(appView.withLiveness()).run(null, executorService); + new RedundantBridgeRemover(appView.withLiveness()).run(null, executorService, timing); } // This pass attempts to reduce the number of nests and nest size to allow further passes, and @@ -595,8 +594,6 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO options.reporter, options.usageInformationConsumer); } - appView.pruneItems(prunedItems, executorService); - new BridgeHoisting(appViewWithLiveness).run(executorService, timing); assert Inliner.verifyAllSingleCallerMethodsHaveBeenPruned(appViewWithLiveness); @@ -684,7 +681,7 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO // This can only be done if we have AppInfoWithLiveness. if (appView.appInfo().hasLiveness()) { new RedundantBridgeRemover(appView.withLiveness()) - .run(memberRebindingIdentityLens, executorService); + .run(memberRebindingIdentityLens, executorService, timing); } else { // If we don't have AppInfoWithLiveness here, it must be because we are not shrinking. When // we are not shrinking, we can't move visibility bridges. In principle, though, it would be diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java index ec98db153c..7052724470 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfo.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java @@ -13,6 +13,7 @@ import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy; import com.android.tools.r8.utils.BooleanBox; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Timing; import java.util.Collection; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -70,7 +71,8 @@ private AppInfo( this.obsolete = obsolete; } - public AppInfo prunedCopyFrom(PrunedItems prunedItems, ExecutorService executorService) + public AppInfo prunedCopyFrom( + PrunedItems prunedItems, ExecutorService executorService, Timing timing) throws ExecutionException { assert getClass() == AppInfo.class; assert checkIfObsolete(); @@ -78,9 +80,13 @@ public AppInfo prunedCopyFrom(PrunedItems prunedItems, ExecutorService executorS if (prunedItems.isEmpty()) { return this; } - return new AppInfo( - getSyntheticItems().commitPrunedItems(prunedItems), - getMainDexInfo().withoutPrunedItems(prunedItems)); + timing.begin("Pruning AppInfo"); + AppInfo result = + new AppInfo( + getSyntheticItems().commitPrunedItems(prunedItems), + getMainDexInfo().withoutPrunedItems(prunedItems)); + timing.end(); + return result; } public AppInfo rebuildWithMainDexInfo(MainDexInfo mainDexInfo) { diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java index 3040ebe557..5e5d54de3f 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java @@ -18,6 +18,7 @@ import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy; import com.android.tools.r8.utils.Pair; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.TriConsumer; import com.android.tools.r8.utils.TriFunction; @@ -126,18 +127,23 @@ public AppInfoWithClassHierarchy rebuildWithMainDexInfo(MainDexInfo mainDexInfo) @Override public AppInfoWithClassHierarchy prunedCopyFrom( - PrunedItems prunedItems, ExecutorService executorService) throws ExecutionException { + PrunedItems prunedItems, ExecutorService executorService, Timing timing) + throws ExecutionException { assert getClass() == AppInfoWithClassHierarchy.class; assert checkIfObsolete(); assert prunedItems.getPrunedApp() == app(); if (prunedItems.isEmpty()) { return this; } - return new AppInfoWithClassHierarchy( - getSyntheticItems().commitPrunedItems(prunedItems), - getClassToFeatureSplitMap().withoutPrunedItems(prunedItems), - getMainDexInfo().withoutPrunedItems(prunedItems), - getMissingClasses()); + timing.begin("Pruning AppInfoWithClassHierarchy"); + AppInfoWithClassHierarchy result = + new AppInfoWithClassHierarchy( + getSyntheticItems().commitPrunedItems(prunedItems), + getClassToFeatureSplitMap().withoutPrunedItems(prunedItems), + getMainDexInfo().withoutPrunedItems(prunedItems), + getMissingClasses()); + timing.end(); + return result; } public ClassToFeatureSplitMap getClassToFeatureSplitMap() { diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java index 3ef7937687..0c1eb41d8c 100644 --- a/src/main/java/com/android/tools/r8/graph/AppServices.java +++ b/src/main/java/com/android/tools/r8/graph/AppServices.java @@ -156,7 +156,8 @@ private AppServices rewrittenWithLens(GraphLens graphLens) { return new AppServices(appView, rewrittenFeatureMappings.build()); } - public AppServices prunedCopy(PrunedItems prunedItems) { + public AppServices prunedCopy(PrunedItems prunedItems, Timing timing) { + timing.begin("Prune AppServices"); ImmutableMap.Builder>> rewrittenServicesBuilder = ImmutableMap.builder(); for (Entry>> entry : services.entrySet()) { @@ -186,7 +187,9 @@ public AppServices prunedCopy(PrunedItems prunedItems) { rewrittenServicesBuilder.put(entry.getKey(), prunedServiceImplementations); } } - return new AppServices(appView, rewrittenServicesBuilder.build()); + AppServices result = new AppServices(appView, rewrittenServicesBuilder.build()); + timing.end(); + return result; } public boolean verifyRewrittenWithLens() { diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index 0d4e982f77..3b85d38dd9 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -865,47 +865,50 @@ public boolean hasCfByteCodePassThroughMethods() { return !cfByteCodePassThrough.isEmpty(); } - public void pruneItems(PrunedItems prunedItems, ExecutorService executorService) + public void pruneItems(PrunedItems prunedItems, ExecutorService executorService, Timing timing) throws ExecutionException { if (prunedItems.isEmpty()) { assert appInfo().app() == prunedItems.getPrunedApp(); return; } + timing.begin("Prune AppView"); if (appInfo.hasLiveness()) { AppView self = withLiveness(); - self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems, executorService)); + self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems, executorService, timing)); } else if (appInfo.hasClassHierarchy()) { AppView self = withClassHierarchy(); - self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems, executorService)); + self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems, executorService, timing)); } else { - pruneAppInfo(prunedItems, this, executorService); + pruneAppInfo(prunedItems, this, executorService, timing); } if (appServices() != null) { - setAppServices(appServices().prunedCopy(prunedItems)); + setAppServices(appServices().prunedCopy(prunedItems, timing)); } - setArtProfileCollection(getArtProfileCollection().withoutPrunedItems(prunedItems)); - setAssumeInfoCollection(getAssumeInfoCollection().withoutPrunedItems(prunedItems)); + setArtProfileCollection(getArtProfileCollection().withoutPrunedItems(prunedItems, timing)); + setAssumeInfoCollection(getAssumeInfoCollection().withoutPrunedItems(prunedItems, timing)); if (hasProguardCompatibilityActions()) { setProguardCompatibilityActions( - getProguardCompatibilityActions().withoutPrunedItems(prunedItems)); + getProguardCompatibilityActions().withoutPrunedItems(prunedItems, timing)); } if (hasRootSet()) { - rootSet.pruneItems(prunedItems); + rootSet.pruneItems(prunedItems, timing); } - setStartupProfile(getStartupProfile().withoutPrunedItems(prunedItems, getSyntheticItems())); + setStartupProfile( + getStartupProfile().withoutPrunedItems(prunedItems, getSyntheticItems(), timing)); if (hasMainDexRootSet()) { - setMainDexRootSet(mainDexRootSet.withoutPrunedItems(prunedItems)); + setMainDexRootSet(mainDexRootSet.withoutPrunedItems(prunedItems, timing)); } setOpenClosedInterfacesCollection( - openClosedInterfacesCollection.withoutPrunedItems(prunedItems)); + openClosedInterfacesCollection.withoutPrunedItems(prunedItems, timing)); + timing.end(); } @SuppressWarnings("unchecked") private static void pruneAppInfo( - PrunedItems prunedItems, AppView appView, ExecutorService executorService) + PrunedItems prunedItems, AppView appView, ExecutorService executorService, Timing timing) throws ExecutionException { ((AppView) appView) - .setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems, executorService)); + .setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems, executorService, timing)); } public void rewriteWithLens( diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java index 3fa5ee2374..9464ef4aa5 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java @@ -216,7 +216,7 @@ private void run( } appView.pruneItems( - prunedItems.toBuilder().setPrunedApp(appView.app()).build(), executorService); + prunedItems.toBuilder().setPrunedApp(appView.app()).build(), executorService, timing); amendKeepInfo(horizontalClassMergerGraphLens, virtuallyMergedMethodsKeepInfos); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java index f046229a83..f8946d4f76 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java @@ -252,7 +252,8 @@ public void waveDone(ProgramMethodSet wave, ExecutorService executorService) .setRemovedMethods(prunedMethodsInWave) .setPrunedApp(appView.appInfo().app()) .build(), - executorService); + executorService, + timing); prunedMethodsInWave.clear(); } } diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java index 7db0c3ad56..564d133cf8 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java @@ -37,7 +37,7 @@ public OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens, Tim } @Override - public OpenClosedInterfacesCollection withoutPrunedItems(PrunedItems prunedItems) { + public OpenClosedInterfacesCollection withoutPrunedItems(PrunedItems prunedItems, Timing timing) { return this; } } diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java index 2bcb577d9d..631221605f 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java @@ -47,16 +47,20 @@ private OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens) { } @Override - public OpenClosedInterfacesCollection withoutPrunedItems(PrunedItems prunedItems) { + public OpenClosedInterfacesCollection withoutPrunedItems(PrunedItems prunedItems, Timing timing) { if (!prunedItems.hasRemovedClasses()) { return this; } + timing.begin("Prune NonEmptyOpenClosedInterfacesCollection"); Set prunedOpenInterfaceTypes = SetUtils.newIdentityHashSet(openInterfaceTypes.size()); for (DexType openInterfaceType : openInterfaceTypes) { if (!prunedItems.isRemoved(openInterfaceType)) { prunedOpenInterfaceTypes.add(openInterfaceType); } } - return new NonEmptyOpenClosedInterfacesCollection(prunedOpenInterfaceTypes); + NonEmptyOpenClosedInterfacesCollection result = + new NonEmptyOpenClosedInterfacesCollection(prunedOpenInterfaceTypes); + timing.end(); + return result; } } diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java index 6c81c5041d..b8d14cccfd 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java @@ -99,5 +99,6 @@ public final boolean isDefinitelyInstanceOfStaticType( public abstract OpenClosedInterfacesCollection rewrittenWithLens( GraphLens graphLens, Timing timing); - public abstract OpenClosedInterfacesCollection withoutPrunedItems(PrunedItems prunedItems); + public abstract OpenClosedInterfacesCollection withoutPrunedItems( + PrunedItems prunedItems, Timing timing); } diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java index 2ed2ab525c..4a3c874a29 100644 --- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java +++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java @@ -26,6 +26,7 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.KeepMethodInfo; import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.util.Collection; import java.util.List; @@ -112,18 +113,23 @@ private boolean isTargetingSuperMethod(ProgramMethod method, InvokeKind kind, De } public void run( - MemberRebindingIdentityLens memberRebindingIdentityLens, ExecutorService executorService) + MemberRebindingIdentityLens memberRebindingIdentityLens, + ExecutorService executorService, + Timing timing) throws ExecutionException { assert memberRebindingIdentityLens == null || memberRebindingIdentityLens == appView.graphLens(); + timing.begin("Redundant bridge removal"); + // Collect all redundant bridges to remove. ProgramMethodSet bridgesToRemove = removeRedundantBridgesConcurrently(executorService); if (bridgesToRemove.isEmpty()) { + timing.end(); return; } - pruneApp(bridgesToRemove, executorService); + pruneApp(bridgesToRemove, executorService, timing); if (!lensBuilder.isEmpty()) { appView.setGraphLens(lensBuilder.build(appView)); @@ -140,6 +146,8 @@ public void run( bridgeToRemove.getReference(), resolvedMethod.getReference()); } } + + timing.end(); } private ProgramMethodSet removeRedundantBridgesConcurrently(ExecutorService executorService) @@ -225,11 +233,12 @@ private boolean isRedundantAbstractBridge(ProgramMethod method) { return true; } - private void pruneApp(ProgramMethodSet bridgesToRemove, ExecutorService executorService) + private void pruneApp( + ProgramMethodSet bridgesToRemove, ExecutorService executorService, Timing timing) throws ExecutionException { PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app()); bridgesToRemove.forEach(method -> prunedItemsBuilder.addRemovedMethod(method.getReference())); - appView.pruneItems(prunedItemsBuilder.build(), executorService); + appView.pruneItems(prunedItemsBuilder.build(), executorService, timing); } class RedundantBridgeRemoverClassHierarchyTraversal diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java index 2a3f889573..2b2f387991 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java @@ -79,5 +79,5 @@ public abstract ArtProfileCollection rewrittenWithLens( public abstract ArtProfileCollection withoutMissingItems(AppView appView); - public abstract ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems); + public abstract ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems, Timing timing); } diff --git a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java index fcd6d413de..ab478b85c8 100644 --- a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java +++ b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java @@ -56,7 +56,7 @@ public ArtProfileCollection withoutMissingItems(AppView appView) { } @Override - public ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems) { + public ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems, Timing timing) { return this; } } diff --git a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java index 51ee7e65be..51132d8ebf 100644 --- a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java +++ b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java @@ -99,8 +99,12 @@ public ArtProfileCollection withoutMissingItems(AppView appView) { } @Override - public NonEmptyArtProfileCollection withoutPrunedItems(PrunedItems prunedItems) { - return map(artProfile -> artProfile.withoutPrunedItems(prunedItems)); + public NonEmptyArtProfileCollection withoutPrunedItems(PrunedItems prunedItems, Timing timing) { + timing.begin("Prune NonEmptyArtProfileCollection"); + NonEmptyArtProfileCollection result = + map(artProfile -> artProfile.withoutPrunedItems(prunedItems)); + timing.end(); + return result; } private NonEmptyArtProfileCollection map(Function fn) { diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java index 0030057921..a051dc983c 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java +++ b/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java @@ -77,7 +77,7 @@ public StartupProfile withoutMissingItems(AppView appView) { @Override public EmptyStartupProfile withoutPrunedItems( - PrunedItems prunedItems, SyntheticItems syntheticItems) { + PrunedItems prunedItems, SyntheticItems syntheticItems, Timing timing) { return this; } } diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java index 34576c39aa..5ccba978f2 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java +++ b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java @@ -176,18 +176,23 @@ public StartupProfile withoutMissingItems(AppView appView) { } @Override - public StartupProfile withoutPrunedItems(PrunedItems prunedItems, SyntheticItems syntheticItems) { - return transform( - (classRule, builder) -> { - if (!prunedItems.isRemoved(classRule.getReference())) { - builder.addClassRule(classRule); - } - }, - (methodRule, builder) -> { - if (!prunedItems.isRemoved(methodRule.getReference())) { - builder.addMethodRule(methodRule); - } - }); + public StartupProfile withoutPrunedItems( + PrunedItems prunedItems, SyntheticItems syntheticItems, Timing timing) { + timing.begin("Prune NonEmptyStartupProfile"); + StartupProfile result = + transform( + (classRule, builder) -> { + if (!prunedItems.isRemoved(classRule.getReference())) { + builder.addClassRule(classRule); + } + }, + (methodRule, builder) -> { + if (!prunedItems.isRemoved(methodRule.getReference())) { + builder.addMethodRule(methodRule); + } + }); + timing.end(); + return result; } private StartupProfile transform( diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java index cee563773a..8ee1fd578e 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java +++ b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java @@ -143,7 +143,7 @@ public abstract void forEachRule( public abstract StartupProfile withoutMissingItems(AppView appView); public abstract StartupProfile withoutPrunedItems( - PrunedItems prunedItems, SyntheticItems syntheticItems); + PrunedItems prunedItems, SyntheticItems syntheticItems, Timing timing); public static class Builder implements AbstractProfile.Builder< diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java index 7ba4bb8fa2..2f121030e8 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java @@ -1132,13 +1132,15 @@ public KeepInfoCollection getKeepInfo() { */ @Override public AppInfoWithLiveness prunedCopyFrom( - PrunedItems prunedItems, ExecutorService executorService) throws ExecutionException { + PrunedItems prunedItems, ExecutorService executorService, Timing timing) + throws ExecutionException { assert getClass() == AppInfoWithLiveness.class; assert checkIfObsolete(); if (prunedItems.isEmpty()) { assert app() == prunedItems.getPrunedApp(); return this; } + timing.begin("Pruning AppInfoWithLiveness"); if (prunedItems.hasRemovedClasses()) { // Rebuild the hierarchy. objectAllocationInfoCollection.mutate( @@ -1151,6 +1153,7 @@ public AppInfoWithLiveness prunedCopyFrom( AppInfoWithLiveness appInfoWithLiveness = new AppInfoWithLiveness(this, prunedItems, executorService, futures); ThreadUtils.awaitFutures(futures); + timing.end(); return appInfoWithLiveness; } diff --git a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java index c45b7e34a0..dd3504b833 100644 --- a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java @@ -85,7 +85,8 @@ private AssumeInfoCollection rewrittenWithLens( return new AssumeInfoCollection(rewrittenCollection); } - public AssumeInfoCollection withoutPrunedItems(PrunedItems prunedItems) { + public AssumeInfoCollection withoutPrunedItems(PrunedItems prunedItems, Timing timing) { + timing.begin("Prune AssumeInfoCollection"); Map, AssumeInfo> rewrittenCollection = new IdentityHashMap<>(); backing.forEach( (reference, info) -> { @@ -96,7 +97,9 @@ public AssumeInfoCollection withoutPrunedItems(PrunedItems prunedItems) { } } }); - return new AssumeInfoCollection(rewrittenCollection); + AssumeInfoCollection result = new AssumeInfoCollection(rewrittenCollection); + timing.end(); + return result; } public static class Builder { diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java index 373b1e1015..88f5f127cf 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java @@ -32,14 +32,17 @@ public boolean isEmpty() { return compatInstantiatedTypes.isEmpty(); } - public ProguardCompatibilityActions withoutPrunedItems(PrunedItems prunedItems) { + public ProguardCompatibilityActions withoutPrunedItems(PrunedItems prunedItems, Timing timing) { + timing.begin("Prune ProguardCompatibilityActions"); Builder builder = builder(); for (DexType compatInstantiatedType : compatInstantiatedTypes) { if (!prunedItems.isRemoved(compatInstantiatedType)) { builder.addCompatInstantiatedType(compatInstantiatedType); } } - return builder.build(); + ProguardCompatibilityActions result = builder.build(); + timing.end(); + return result; } public ProguardCompatibilityActions rewrittenWithLens(GraphLens lens, Timing timing) { diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java index ad7c3881a5..192de9222c 100644 --- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java +++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java @@ -1948,7 +1948,8 @@ private static void pruneDeadReferences( }); } - public void pruneItems(PrunedItems prunedItems) { + public void pruneItems(PrunedItems prunedItems, Timing timing) { + timing.begin("Prune RootSet"); MinimumKeepInfoCollection unconditionalMinimumKeepInfo = getDependentMinimumKeepInfo().getUnconditionalMinimumKeepInfoOrDefault(null); if (unconditionalMinimumKeepInfo != null) { @@ -1957,6 +1958,7 @@ public void pruneItems(PrunedItems prunedItems) { getDependentMinimumKeepInfo().remove(UnconditionalKeepInfoEvent.get()); } } + timing.end(); } public RootSet rewrittenWithLens(GraphLens graphLens, Timing timing) { @@ -2343,20 +2345,21 @@ public MainDexRootSet rewrittenWithLens(GraphLens graphLens, Timing timing) { return rewrittenMainDexRootSet; } - public MainDexRootSet withoutPrunedItems(PrunedItems prunedItems) { + public MainDexRootSet withoutPrunedItems(PrunedItems prunedItems, Timing timing) { if (prunedItems.isEmpty()) { return this; } + timing.begin("Prune MainDexRootSet"); // TODO(b/164019179): If rules can now reference dead items. These should be pruned or // rewritten. ifRules.forEach(ProguardIfRule::canReferenceDeadTypes); // All delayed root set actions should have been processed at this point. assert delayedRootSetActionItems.isEmpty(); - return new MainDexRootSet( - getDependentMinimumKeepInfo(), - reasonAsked, - ifRules, - delayedRootSetActionItems); + MainDexRootSet result = + new MainDexRootSet( + getDependentMinimumKeepInfo(), reasonAsked, ifRules, delayedRootSetActionItems); + timing.end(); + return result; } } } diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java index d8c17ba75a..fddfe6804a 100644 --- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java +++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java @@ -76,13 +76,7 @@ public TreePruner(AppView appView, TreePrunerConfiguration public PrunedItems run( ExecutorService executorService, Timing timing, PrunedItems.Builder prunedItemsBuilder) throws ExecutionException { - return timing.time( - "Pruning application...", () -> internalRun(executorService, prunedItemsBuilder)); - } - - private PrunedItems internalRun( - ExecutorService executorService, PrunedItems.Builder prunedItemsBuilder) - throws ExecutionException { + timing.begin("Pruning application"); DirectMappedDexApplication application = appView.appInfo().app().asDirect(); DirectMappedDexApplication.Builder builder = removeUnused(application); DirectMappedDexApplication newApplication = @@ -90,13 +84,17 @@ private PrunedItems internalRun( ? application : builder.build(); fixupOptimizationInfo(newApplication, executorService); - return prunedItemsBuilder - .setPrunedApp(newApplication) - .addRemovedClasses(prunedTypes) - .addRemovedFields(prunedFields) - .addRemovedMethods(prunedMethods) - .addAdditionalPinnedItems(methodsToKeepForConfigurationDebugging) - .build(); + PrunedItems prunedItems = + prunedItemsBuilder + .setPrunedApp(newApplication) + .addRemovedClasses(prunedTypes) + .addRemovedFields(prunedFields) + .addRemovedMethods(prunedMethods) + .addAdditionalPinnedItems(methodsToKeepForConfigurationDebugging) + .build(); + appView.pruneItems(prunedItems, executorService, timing); + timing.end(); + return prunedItems; } private DirectMappedDexApplication.Builder removeUnused(DirectMappedDexApplication application) { diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java index cf3d62777f..e7c6a53e91 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java @@ -182,7 +182,7 @@ public static void finalize( .rewrittenWithLens(appView.getSyntheticItems(), result.lens, timing))); appView.rewriteWithD8Lens(result.lens, timing); } - appView.pruneItems(result.prunedItems, executorService); + appView.pruneItems(result.prunedItems, executorService, timing); } public static void finalizeWithClassHierarchy( @@ -196,7 +196,7 @@ public static void finalizeWithClassHierarchy( if (result.lens != null) { appView.rewriteWithLens(result.lens, executorService, timing); } - appView.pruneItems(result.prunedItems, executorService); + appView.pruneItems(result.prunedItems, executorService, timing); } public static void finalizeWithLiveness( @@ -212,7 +212,7 @@ public static void finalizeWithLiveness( assert result.commit.getApplication() == appView.appInfo().app(); } appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit)); - appView.pruneItems(result.prunedItems, executorService); + appView.pruneItems(result.prunedItems, executorService, timing); } Result computeFinalSynthetics(AppView appView, Timing timing) { From df3cfff203bdd6c2b0c2dd14c67d402e85fbe57d Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Mon, 12 Jun 2023 15:01:06 +0200 Subject: [PATCH 077/153] Add a system property to control Timing.MINIMUM_REPORT_PERCENTAGE Change-Id: Ifca74b6682f1bc28f5546777512f7d7267cbef8f --- src/main/java/com/android/tools/r8/utils/Timing.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java index 93524d1081..92beeb7e31 100644 --- a/src/main/java/com/android/tools/r8/utils/Timing.java +++ b/src/main/java/com/android/tools/r8/utils/Timing.java @@ -23,7 +23,9 @@ public class Timing { - private static final int MINIMUM_REPORT_PERCENTAGE = 2; + private static final int MINIMUM_REPORT_PERCENTAGE = + SystemPropertyUtils.parseSystemPropertyOrDefault( + "com.android.tools.r8.printtimes.minvalue", 2); private static final Timing EMPTY = new Timing("", false) { From 8e79a930a6d7697c1cd70909441ced7f4a721e4d Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Mon, 12 Jun 2023 14:50:24 +0200 Subject: [PATCH 078/153] Optimize rewriting of field collections Change-Id: Id768a3bf91e87a456eeb368f8c0c35ef29cf1e2c --- .../r8/graph/AbstractAccessContexts.java | 59 +++++++++++++++---- .../tools/r8/graph/FieldAccessInfoImpl.java | 41 +++++++++++-- .../tools/r8/graph/lens/GraphLens.java | 49 +++++++++++++++ .../r8/graph/lens/IdentityGraphLens.java | 5 ++ .../r8/graph/lens/NonIdentityGraphLens.java | 5 ++ .../r8/optimize/MemberRebindingLens.java | 8 +++ .../tools/r8/shaking/AppInfoWithLiveness.java | 2 +- .../tools/r8/utils/CollectionUtils.java | 21 +++++++ .../com/android/tools/r8/utils/MapUtils.java | 34 +++++++++++ .../com/android/tools/r8/utils/SetUtils.java | 10 ++++ .../utils/collections/ProgramMethodSet.java | 54 +++++++++++++---- 11 files changed, 258 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java index 4c63987c76..0229986bf3 100644 --- a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java +++ b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java @@ -6,9 +6,11 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.lens.GraphLens; +import com.android.tools.r8.utils.MapUtils; import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.util.IdentityHashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; @@ -306,19 +308,50 @@ public boolean recordAccess(DexField access, ProgramMethod context) { @Override ConcreteAccessContexts rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) { - Map newAccessesWithContexts = new IdentityHashMap<>(); - accessesWithContexts.forEach( - (access, contexts) -> { - ProgramMethodSet newContexts = - newAccessesWithContexts.computeIfAbsent( - lens.lookupField(access), ignore -> ProgramMethodSet.create()); - for (ProgramMethod context : contexts) { - ProgramMethod newContext = lens.mapProgramMethod(context, definitions); - assert newContext != null : "Unable to map context: " + context.toSourceString(); - newContexts.add(newContext); - } - }); - return new ConcreteAccessContexts(newAccessesWithContexts); + Map rewrittenAccessesWithContexts = null; + for (Entry entry : accessesWithContexts.entrySet()) { + DexField field = entry.getKey(); + DexField rewrittenField = lens.lookupField(field); + + ProgramMethodSet contexts = entry.getValue(); + ProgramMethodSet rewrittenContexts = contexts.rewrittenWithLens(definitions, lens); + + if (rewrittenField == field && rewrittenContexts == contexts) { + if (rewrittenAccessesWithContexts == null) { + continue; + } + } else { + if (rewrittenAccessesWithContexts == null) { + rewrittenAccessesWithContexts = new IdentityHashMap<>(accessesWithContexts.size()); + MapUtils.forEachUntilExclusive( + accessesWithContexts, rewrittenAccessesWithContexts::put, field); + } + } + merge(rewrittenAccessesWithContexts, rewrittenField, rewrittenContexts); + } + if (rewrittenAccessesWithContexts != null) { + rewrittenAccessesWithContexts = + MapUtils.trimCapacityOfIdentityHashMapIfSizeLessThan( + rewrittenAccessesWithContexts, accessesWithContexts.size()); + return new ConcreteAccessContexts(rewrittenAccessesWithContexts); + } else { + return this; + } + } + + private static void merge( + Map accessesWithContexts, + DexField field, + ProgramMethodSet contexts) { + ProgramMethodSet existingContexts = accessesWithContexts.put(field, contexts); + if (existingContexts != null) { + if (existingContexts.size() <= contexts.size()) { + contexts.addAll(existingContexts); + } else { + accessesWithContexts.put(field, existingContexts); + existingContexts.addAll(contexts); + } + } } @Override diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java index 1bc6ae6298..4344d55b1a 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java @@ -41,14 +41,25 @@ public class FieldAccessInfoImpl implements FieldAccessInfo { // Maps every direct and indirect reference in a read-context to the set of methods in which that // reference appears. - private AbstractAccessContexts readsWithContexts = AbstractAccessContexts.empty(); + private AbstractAccessContexts readsWithContexts; // Maps every direct and indirect reference in a write-context to the set of methods in which that // reference appears. - private AbstractAccessContexts writesWithContexts = AbstractAccessContexts.empty(); + private AbstractAccessContexts writesWithContexts; public FieldAccessInfoImpl(DexField field) { + this(field, 0, AbstractAccessContexts.empty(), AbstractAccessContexts.empty()); + } + + public FieldAccessInfoImpl( + DexField field, + int flags, + AbstractAccessContexts readsWithContexts, + AbstractAccessContexts writesWithContexts) { this.field = field; + this.flags = flags; + this.readsWithContexts = readsWithContexts; + this.writesWithContexts = writesWithContexts; } void destroyAccessContexts() { @@ -360,10 +371,28 @@ public void clearWrites() { public FieldAccessInfoImpl rewrittenWithLens( DexDefinitionSupplier definitions, GraphLens lens, Timing timing) { timing.begin("Rewrite FieldAccessInfoImpl"); - FieldAccessInfoImpl rewritten = new FieldAccessInfoImpl(lens.lookupField(field)); - rewritten.flags = flags; - rewritten.readsWithContexts = readsWithContexts.rewrittenWithLens(definitions, lens); - rewritten.writesWithContexts = writesWithContexts.rewrittenWithLens(definitions, lens); + AbstractAccessContexts rewrittenReadsWithContexts = + readsWithContexts.rewrittenWithLens(definitions, lens); + AbstractAccessContexts rewrittenWritesWithContexts = + writesWithContexts.rewrittenWithLens(definitions, lens); + FieldAccessInfoImpl rewritten; + if (lens.isIdentityLensForFields(GraphLens.getIdentityLens())) { + if (rewrittenReadsWithContexts == readsWithContexts + && rewrittenWritesWithContexts == writesWithContexts) { + rewritten = this; + } else { + rewritten = + new FieldAccessInfoImpl( + field, flags, rewrittenReadsWithContexts, rewrittenWritesWithContexts); + } + } else { + rewritten = + new FieldAccessInfoImpl( + lens.lookupField(field), + flags, + rewrittenReadsWithContexts, + rewrittenWritesWithContexts); + } timing.end(); return rewritten; } diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index 43deea367c..5c884ce726 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java @@ -29,6 +29,7 @@ import com.android.tools.r8.optimize.MemberRebindingIdentityLens; import com.android.tools.r8.optimize.MemberRebindingLens; import com.android.tools.r8.shaking.KeepInfoCollection; +import com.android.tools.r8.utils.CollectionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.SetUtils; @@ -42,6 +43,7 @@ import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; import java.util.ArrayList; +import java.util.Collection; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -432,6 +434,8 @@ public boolean isSimpleRenamingLens() { public abstract boolean isIdentityLens(); + public abstract boolean isIdentityLensForFields(GraphLens codeLens); + public boolean isMemberRebindingLens() { return false; } @@ -523,6 +527,51 @@ public Map rewriteCallSites( return result; } + public Set rewriteFields(Set fields, Timing timing) { + timing.begin("Rewrite fields"); + GraphLens appliedLens = getIdentityLens(); + Set rewrittenFields; + if (isIdentityLensForFields(appliedLens)) { + assert verifyIsIdentityLensForFields(fields, appliedLens); + rewrittenFields = fields; + } else { + rewrittenFields = null; + for (DexField field : fields) { + DexField rewrittenField = getRenamedFieldSignature(field, appliedLens); + // If rewrittenFields is non-null we have previously seen a change and need to record the + // field no matter what. + if (rewrittenFields != null) { + rewrittenFields.add(rewrittenField); + continue; + } + // If the field has not been rewritten then we can reuse the input set. + if (rewrittenField == field) { + continue; + } + // Otherwise add the rewritten field and all previous fields to a new set. + rewrittenFields = SetUtils.newIdentityHashSet(fields.size()); + CollectionUtils.forEachUntilExclusive(fields, rewrittenFields::add, field); + rewrittenFields.add(rewrittenField); + } + if (rewrittenFields == null) { + rewrittenFields = fields; + } else { + rewrittenFields = + SetUtils.trimCapacityOfIdentityHashSetIfSizeLessThan(rewrittenFields, fields.size()); + } + } + timing.end(); + return rewrittenFields; + } + + private boolean verifyIsIdentityLensForFields( + Collection fields, GraphLens appliedLens) { + for (DexField field : fields) { + assert lookupField(field, appliedLens) == field; + } + return true; + } + @SuppressWarnings("unchecked") public T rewriteReference(T reference) { return rewriteReference(reference, null); diff --git a/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java index 2d6b49eb99..02c6a27a11 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java @@ -26,6 +26,11 @@ public boolean isIdentityLens() { return true; } + @Override + public boolean isIdentityLensForFields(GraphLens codeLens) { + return true; + } + @Override public boolean isNonIdentityLens() { return false; diff --git a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java index ac9d814b2e..ccbacc3e64 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java @@ -189,6 +189,11 @@ public final boolean isIdentityLens() { return false; } + @Override + public boolean isIdentityLensForFields(GraphLens codeLens) { + return this == codeLens; + } + @Override public final boolean isNonIdentityLens() { return true; diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java index d94e65d4d4..15b6ae9959 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java @@ -85,6 +85,14 @@ public MethodLookupResult internalDescribeLookupMethod( .build(); } + @Override + public boolean isIdentityLensForFields(GraphLens codeLens) { + if (this == codeLens) { + return true; + } + return getPrevious().isIdentityLensForFields(codeLens); + } + public FieldRebindingIdentityLens toRewrittenFieldRebindingLens( AppView appView, GraphLens lens, diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java index 2f121030e8..05112d6c34 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java @@ -1186,7 +1186,7 @@ public AppInfoWithLiveness rewrittenWithLens( lens.rewriteReferences(liveTypes), lens.rewriteReferences(targetedMethods), lens.rewriteReferences(failedMethodResolutionTargets), - lens.rewriteReferences(failedFieldResolutionTargets), + lens.rewriteFields(failedFieldResolutionTargets, timing), lens.rewriteReferences(bootstrapMethods), lens.rewriteReferences(virtualMethodsTargetedByInvokeDirect), lens.rewriteReferences(liveMethods), diff --git a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java index 9a9d27cadb..543487137d 100644 --- a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java +++ b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java @@ -12,6 +12,7 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; public class CollectionUtils { @@ -42,6 +43,26 @@ public static void forEach(Consumer consumer, Collection... collection } } + public static void forEachUntilExclusive( + Collection collection, Consumer consumer, T stoppingCriterion) { + for (T element : collection) { + if (element.equals(stoppingCriterion)) { + break; + } + consumer.accept(element); + } + } + + public static void forEachUntilExclusive( + Collection collection, Consumer consumer, Predicate stoppingCriterion) { + for (T element : collection) { + if (stoppingCriterion.test(element)) { + break; + } + consumer.accept(element); + } + } + public static > Collection sort(Collection items) { ArrayList ts = new ArrayList<>(items); Collections.sort(ts); diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java index 57572184e0..ed49f663d2 100644 --- a/src/main/java/com/android/tools/r8/utils/MapUtils.java +++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java @@ -12,6 +12,7 @@ import java.util.IdentityHashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Consumer; @@ -39,6 +40,17 @@ public static V firstValue(Map map) { return map.values().iterator().next(); } + public static void forEachUntilExclusive( + Map map, BiConsumer consumer, K stoppingCriterion) { + for (Entry entry : map.entrySet()) { + K key = entry.getKey(); + if (key.equals(stoppingCriterion)) { + break; + } + consumer.accept(key, entry.getValue()); + } + } + public static Function ignoreKey(Supplier supplier) { return ignore -> supplier.get(); } @@ -133,6 +145,28 @@ public static boolean equals(Map one, Map other) { return true; } + public static Map trimCapacity(Map map, IntFunction> mapFactory) { + Map newMap = mapFactory.apply(map.size()); + newMap.putAll(map); + return newMap; + } + + public static Map trimCapacityIfSizeLessThan( + Map map, IntFunction> mapFactory, int expectedSize) { + if (map.size() < expectedSize) { + return trimCapacity(map, mapFactory); + } + return map; + } + + public static Map trimCapacityOfIdentityHashMapIfSizeLessThan( + Map map, int expectedSize) { + if (map.size() < expectedSize) { + return trimCapacity(map, IdentityHashMap::new); + } + return map; + } + public static Map unmodifiableForTesting(Map map) { return InternalOptions.assertionsEnabled() ? Collections.unmodifiableMap(map) : map; } diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java index a0ecdfc100..05af36b61e 100644 --- a/src/main/java/com/android/tools/r8/utils/SetUtils.java +++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java @@ -125,6 +125,16 @@ public static T removeFirst(Set set) { return element; } + public static Set trimCapacityOfIdentityHashSetIfSizeLessThan( + Set set, int expectedSize) { + if (set.size() < expectedSize) { + Set newSet = SetUtils.newIdentityHashSet(set.size()); + newSet.addAll(set); + return newSet; + } + return set; + } + public static Set unionIdentityHashSet(Set one, Set other) { Set union = Sets.newIdentityHashSet(); union.addAll(one); diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java index 612ace4220..0ac1e7cc89 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java @@ -11,9 +11,12 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.graph.lens.GraphLens; +import com.android.tools.r8.utils.CollectionUtils; import com.android.tools.r8.utils.ForEachable; import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -84,17 +87,48 @@ public boolean createAndAdd(DexProgramClass clazz, DexEncodedMethod definition) } public ProgramMethodSet rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) { - ProgramMethodSet rewritten = ProgramMethodSet.create(size()); - forEach( - method -> { - ProgramMethod newMethod = lens.mapProgramMethod(method, definitions); - if (newMethod != null) { - assert !newMethod.getDefinition().isObsolete(); - rewritten.add(newMethod); + List elementsToRemove = null; + ProgramMethodSet rewrittenMethods = null; + for (ProgramMethod method : this) { + ProgramMethod rewrittenMethod = lens.mapProgramMethod(method, definitions); + if (rewrittenMethod == null) { + assert lens.isEnumUnboxerLens(); + // If everything has been unchanged up until now, then record that we should remove this + // method. + if (rewrittenMethods == null) { + if (elementsToRemove == null) { + elementsToRemove = new ArrayList<>(); } - }); - rewritten.trimCapacityIfSizeLessThan(size()); - return rewritten; + elementsToRemove.add(method); + } + continue; + } + if (rewrittenMethod == method) { + if (rewrittenMethods != null) { + rewrittenMethods.add(rewrittenMethod); + } + } else { + if (rewrittenMethods == null) { + rewrittenMethods = ProgramMethodSet.create(size()); + CollectionUtils.forEachUntilExclusive( + this, rewrittenMethods::add, method::isStructurallyEqualTo); + if (elementsToRemove != null) { + rewrittenMethods.removeAll(elementsToRemove); + elementsToRemove = null; + } + } + rewrittenMethods.add(rewrittenMethod); + } + } + if (rewrittenMethods != null) { + rewrittenMethods.trimCapacityIfSizeLessThan(size()); + return rewrittenMethods; + } else { + if (elementsToRemove != null) { + removeAll(elementsToRemove); + } + return this; + } } public ProgramMethodSet withoutPrunedItems(PrunedItems prunedItems) { From f8dc3de298baeffafdbea94a49d8e0b1458b5639 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Mon, 12 Jun 2023 20:24:48 +0200 Subject: [PATCH 079/153] Introduce a callback for when optimizations that change the app finish Change-Id: I5ff0139e27d93f231a760091adab984909ffcfc2 --- src/main/java/com/android/tools/r8/graph/AppView.java | 8 ++++++++ .../tools/r8/graph/MethodAccessInfoCollection.java | 4 +++- .../r8/horizontalclassmerging/HorizontalClassMerger.java | 1 + .../com/android/tools/r8/ir/optimize/NestReducer.java | 1 + .../tools/r8/ir/optimize/enums/EnumUnboxerImpl.java | 1 + .../tools/r8/ir/optimize/outliner/OutlinerImpl.java | 1 + src/main/java/com/android/tools/r8/naming/Minifier.java | 1 + .../com/android/tools/r8/naming/ProguardMapMinifier.java | 1 + .../com/android/tools/r8/optimize/AccessModifier.java | 2 ++ .../tools/r8/optimize/MemberRebindingAnalysis.java | 1 + .../optimize/argumentpropagation/ArgumentPropagator.java | 2 ++ .../tools/r8/optimize/bridgehoisting/BridgeHoisting.java | 2 ++ .../android/tools/r8/optimize/fields/FieldFinalizer.java | 1 + .../android/tools/r8/optimize/proto/ProtoNormalizer.java | 1 + .../redundantbridgeremoval/RedundantBridgeRemover.java | 1 + .../com/android/tools/r8/repackaging/Repackaging.java | 1 + .../android/tools/r8/shaking/AbstractMethodRemover.java | 1 + .../tools/r8/shaking/ClassInitFieldSynthesizer.java | 1 + .../java/com/android/tools/r8/shaking/TreePruner.java | 1 + .../com/android/tools/r8/shaking/VerticalClassMerger.java | 1 + .../android/tools/r8/synthesis/SyntheticFinalization.java | 2 ++ 21 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index 3b85d38dd9..9848f4b1db 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -1180,6 +1180,14 @@ public void setAlreadyLibraryDesugared(Set alreadyLibraryDesugared) { this.alreadyLibraryDesugared = alreadyLibraryDesugared; } + /** + * Called when an optimization that changes the app has finished. This allows easier diagnosing + * some failures, e.g., finding which optimization pass that adds/removes a given method. + */ + public void notifyOptimizationFinishedForTesting() { + // Intentionally empty. + } + public boolean isAlreadyLibraryDesugared(DexProgramClass clazz) { if (!options().desugarSpecificOptions().allowAllDesugaredInput) { return false; diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java index 68f9b0a16c..e3b45a8c47 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java +++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java @@ -184,7 +184,9 @@ private static void pruneItems( if (prunedItems.isRemoved(context.getReference())) { return true; } - assert prunedItems.getPrunedApp().definitionFor(context.getReference()) != null; + assert prunedItems.getPrunedApp().definitionFor(context.getReference()) != null + : "Expected method to be present: " + + context.getReference().toSourceString(); return false; }); return contexts.isEmpty(); diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java index 9464ef4aa5..9f2e2ad999 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java @@ -96,6 +96,7 @@ public void runIfNecessary( // Clear type elements cache after IR building. appView.dexItemFactory().clearTypeElementsCache(); + appView.notifyOptimizationFinishedForTesting(); timing.end(); } else { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java b/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java index e20e28c4fe..80ea1ad132 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java @@ -43,6 +43,7 @@ public void run(ExecutorService executorService, Timing timing) throws Execution } else { reduceNests(executorService); } + appView.notifyOptimizationFinishedForTesting(); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java index 5b8e1f7eab..115ab23e79 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java @@ -757,6 +757,7 @@ public void unboxEnums( // Ensure determinism of method-to-reprocess set. appView.testing().checkDeterminism(postMethodProcessorBuilder::dump); + appView.notifyOptimizationFinishedForTesting(); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java index 3b0caa254f..9cd00c7b17 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java @@ -1378,6 +1378,7 @@ public void performOutlining( outlineMethods.forEach(m -> m.getDefinition().markNotProcessed()); eventConsumer.finished(appView); } + appView.notifyOptimizationFinishedForTesting(); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java index 52fee793d8..229de6aef7 100644 --- a/src/main/java/com/android/tools/r8/naming/Minifier.java +++ b/src/main/java/com/android/tools/r8/naming/Minifier.java @@ -86,6 +86,7 @@ assert new MinifiedRenaming(appView, classRenaming, methodRenaming, FieldRenamin new IdentifierMinifier(appView, lens).run(executorService); timing.end(); + appView.notifyOptimizationFinishedForTesting(); return lens; } diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java index 3c128fba90..38a950d8b3 100644 --- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java @@ -157,6 +157,7 @@ public NamingLens run(ExecutorService executorService, Timing timing) throws Exe new IdentifierMinifier(appView, lens).run(executorService); timing.end(); + appView.notifyOptimizationFinishedForTesting(); return lens; } diff --git a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java index 410bec21ac..dea634656f 100644 --- a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java +++ b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java @@ -81,6 +81,8 @@ private void internalRun(ExecutorService executorService, Timing timing) if (publicizerLens != null) { appView.setGraphLens(publicizerLens); } + + appView.notifyOptimizationFinishedForTesting(); } private void doPublicize(ProgramDefinition definition) { diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java index ca5ae4aa55..41b4f59b6c 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java @@ -526,6 +526,7 @@ public void run(ExecutorService executorService) throws ExecutionException { MemberRebindingLens memberRebindingLens = lensBuilder.build(); appView.setGraphLens(memberRebindingLens); eventConsumer.finished(appView, memberRebindingLens); + appView.notifyOptimizationFinishedForTesting(); } private boolean verifyFieldAccessCollectionContainsAllNonReboundFieldReferences( diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java index 2d47dccbe2..8a0031814e 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java @@ -199,6 +199,8 @@ public void tearDownCodeScanner( // Ensure determinism of method-to-reprocess set. appView.testing().checkDeterminism(postMethodProcessorBuilder::dump); + + appView.notifyOptimizationFinishedForTesting(); } /** diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java index 23f81225ca..1e147e2c1e 100644 --- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java +++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java @@ -122,6 +122,8 @@ public void run(ExecutorService executorService, Timing timing) throws Execution } }); } + + appView.notifyOptimizationFinishedForTesting(); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/optimize/fields/FieldFinalizer.java b/src/main/java/com/android/tools/r8/optimize/fields/FieldFinalizer.java index 9b4bec4543..154c354a0b 100644 --- a/src/main/java/com/android/tools/r8/optimize/fields/FieldFinalizer.java +++ b/src/main/java/com/android/tools/r8/optimize/fields/FieldFinalizer.java @@ -30,6 +30,7 @@ public static void run( AppView appView, ExecutorService executorService, Timing timing) throws ExecutionException { timing.time("Finalize fields pass", () -> run(appView, executorService)); + appView.notifyOptimizationFinishedForTesting(); } private static void run(AppView appView, ExecutorService executorService) diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java index 54ae131916..47df2baac8 100644 --- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java +++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java @@ -153,6 +153,7 @@ protected TraversalContinuation joiner( if (!lensBuilder.isEmpty()) { appView.rewriteWithLens(lensBuilder.build(), executorService, timing); } + appView.notifyOptimizationFinishedForTesting(); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java index 4a3c874a29..73a0444ddb 100644 --- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java +++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java @@ -147,6 +147,7 @@ public void run( } } + appView.notifyOptimizationFinishedForTesting(); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java index 77be1dc188..f4c023ffd2 100644 --- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java +++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java @@ -77,6 +77,7 @@ public void run(ExecutorService executorService, Timing timing) throws Execution if (lens != null) { appView.rewriteWithLensAndApplication(lens, appBuilder.build(), executorService, timing); } + appView.notifyOptimizationFinishedForTesting(); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java index d941a87afe..a5854e2f79 100644 --- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java +++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java @@ -37,6 +37,7 @@ public AbstractMethodRemover(AppView appView, SubtypingInfo public void run() { assert scope.getParent() == null; processClass(appView.dexItemFactory().objectType); + appView.notifyOptimizationFinishedForTesting(); } private void processClass(DexType type) { diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java index 848ea2a5d7..1888d15d6c 100644 --- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java +++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java @@ -35,6 +35,7 @@ public void run(ExecutorService executorService) throws ExecutionException { ThreadUtils.processMap( appView.appInfo().initClassReferences, this::synthesizeClassInitField, executorService); appView.setInitClassLens(lensBuilder.build()); + appView.notifyOptimizationFinishedForTesting(); } private void synthesizeClassInitField(DexType type, Visibility minimumRequiredVisibility) { diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java index fddfe6804a..3688415793 100644 --- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java +++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java @@ -93,6 +93,7 @@ public PrunedItems run( .addAdditionalPinnedItems(methodsToKeepForConfigurationDebugging) .build(); appView.pruneItems(prunedItems, executorService, timing); + appView.notifyOptimizationFinishedForTesting(); timing.end(); return prunedItems; } diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index 01b19c3cc6..4c152b6f98 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java @@ -658,6 +658,7 @@ public VerticalClassMergerGraphLens run() throws ExecutionException { } }); + appView.notifyOptimizationFinishedForTesting(); return lens; } diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java index e7c6a53e91..9ed366e49c 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java @@ -197,6 +197,7 @@ public static void finalizeWithClassHierarchy( appView.rewriteWithLens(result.lens, executorService, timing); } appView.pruneItems(result.prunedItems, executorService, timing); + appView.notifyOptimizationFinishedForTesting(); } public static void finalizeWithLiveness( @@ -213,6 +214,7 @@ public static void finalizeWithLiveness( } appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit)); appView.pruneItems(result.prunedItems, executorService, timing); + appView.notifyOptimizationFinishedForTesting(); } Result computeFinalSynthetics(AppView appView, Timing timing) { From a95e15841dd9078ae24a3b6b20d13c443338a461 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Mon, 12 Jun 2023 22:17:55 +0200 Subject: [PATCH 080/153] Fix extension of method access info collection Change-Id: I1a0847a33fa9711246ec6218116cafa45d83dee0 --- .../bridgehoisting/BridgeHoisting.java | 16 ++------------ .../bridgehoisting/BridgeHoistingResult.java | 22 ------------------- 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java index 1e147e2c1e..ae0133f57c 100644 --- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java +++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java @@ -93,25 +93,13 @@ public void run(ExecutorService executorService, Timing timing) throws Execution .excludeInterfaces() .visit(appView.appInfo().classes(), clazz -> processClass(clazz, subtypingInfo)); if (!result.isEmpty()) { - // Mapping from non-hoisted bridge methods to the set of contexts in which they are accessed. - MethodAccessInfoCollection.IdentityBuilder bridgeMethodAccessInfoCollectionBuilder = - MethodAccessInfoCollection.identityBuilder(); - result.recordNonReboundMethodAccesses(bridgeMethodAccessInfoCollectionBuilder); - BridgeHoistingLens lens = result.buildLens(); appView.rewriteWithLens(lens, executorService, timing); - // Update method access info collection. + // Record the invokes from the newly synthesized bridge methods in the method access info + // collection. MethodAccessInfoCollection.Modifier methodAccessInfoCollectionModifier = appView.appInfo().getMethodAccessInfoCollection().modifier(); - - // The bridge hoisting lens does not specify any code rewritings. Therefore references to the - // bridge methods are left as-is in the code, but they are rewritten to the hoisted bridges - // during the rewriting of AppInfoWithLiveness. Therefore, this conservatively records that - // there may be an invoke-virtual instruction that targets each of the removed bridges. - methodAccessInfoCollectionModifier.addAll(bridgeMethodAccessInfoCollectionBuilder.build()); - - // Additionally, we record the invokes from the newly synthesized bridge methods. result.forEachHoistedBridge( (bridge, bridgeInfo) -> { if (bridgeInfo.isVirtualBridgeInfo()) { diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java index e1311d5dbe..ce4cc24a0a 100644 --- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java +++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java @@ -7,7 +7,6 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.graph.MethodAccessInfoCollection; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -61,27 +60,6 @@ public void move(Iterable from, DexMethod to, DexMethod representativ bridgeToHoistedBridgeMap.setRepresentative(to, originalRepresentative); } - public void recordNonReboundMethodAccesses( - MethodAccessInfoCollection.IdentityBuilder bridgeMethodAccessInfoCollectionBuilder) { - MethodAccessInfoCollection methodAccessInfoCollection = - appView.appInfo().getMethodAccessInfoCollection(); - bridgeToHoistedBridgeMap - .keySet() - .forEach( - from -> { - methodAccessInfoCollection.forEachSuperInvokeContext( - from, - context -> - bridgeMethodAccessInfoCollectionBuilder.registerInvokeSuperInContext( - from, context)); - methodAccessInfoCollection.forEachVirtualInvokeContext( - from, - context -> - bridgeMethodAccessInfoCollectionBuilder.registerInvokeVirtualInContext( - from, context)); - }); - } - public BridgeHoistingLens buildLens() { assert !isEmpty(); return new BridgeHoistingLens(appView, bridgeToHoistedBridgeMap); From 28fda19bfd18c758a5c00749cc62277000513e5c Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 13 Jun 2023 08:04:20 +0200 Subject: [PATCH 081/153] Temporarily disable concurrent rewriting of AppInfo Fixes: b/286966633 Change-Id: Ia7f532618687446528168e44a66fcf8699579ba1 --- .../com/android/tools/r8/graph/AppView.java | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index 9848f4b1db..e294798cfb 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -963,36 +963,29 @@ private static void rewriteWithLensAndApplication( newMemberRebindingLens, () -> { GraphLens appliedLensInModifiedLens = GraphLens.getIdentityLens(); + if (appView.hasLiveness()) { + appView + .withLiveness() + .setAppInfo( + appView.appInfoWithLiveness().rewrittenWithLens(application, lens, timing)); + } else { + assert appView.hasClassHierarchy(); + AppView appViewWithClassHierarchy = + appView.withClassHierarchy(); + AppInfoWithClassHierarchy appInfo = appViewWithClassHierarchy.appInfo(); + MainDexInfo rewrittenMainDexInfo = + appInfo + .getMainDexInfo() + .rewrittenWithLens(appView.getSyntheticItems(), lens, timing); + appViewWithClassHierarchy.setAppInfo( + appInfo.rebuildWithMainDexInfo(rewrittenMainDexInfo)); + } ThreadTaskUtils.processTasks( executorService, appView.options(), timing .beginMerger("Rewrite AppView concurrently", executorService) .disableSlowestReporting(), - new ThreadTask() { - @Override - public void run(Timing threadTiming) { - if (appView.hasLiveness()) { - appView - .withLiveness() - .setAppInfo( - appView - .appInfoWithLiveness() - .rewrittenWithLens(application, lens, threadTiming)); - } else { - assert appView.hasClassHierarchy(); - AppView appViewWithClassHierarchy = - appView.withClassHierarchy(); - AppInfoWithClassHierarchy appInfo = appViewWithClassHierarchy.appInfo(); - MainDexInfo rewrittenMainDexInfo = - appInfo - .getMainDexInfo() - .rewrittenWithLens(appView.getSyntheticItems(), lens, threadTiming); - appViewWithClassHierarchy.setAppInfo( - appInfo.rebuildWithMainDexInfo(rewrittenMainDexInfo)); - } - } - }, new ThreadTask() { @Override public void run(Timing threadTiming) { From 3da760f07dd99dc1d63b07438ef91e0ed8e97cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gjesse?= Date: Tue, 13 Jun 2023 10:22:51 +0200 Subject: [PATCH 082/153] Keep IR free of redundant blocks Remove redundant blocks from the IR after each rewrite which might introduce them. Bug: b/281975144 Change-Id: I2c799f93882d9b6d509b32952442c81138c82df7 --- .../com/android/tools/r8/ir/code/IRCode.java | 55 +++++++++++++++++++ .../com/android/tools/r8/ir/code/Phi.java | 6 +- .../tools/r8/ir/conversion/CfBuilder.java | 1 + .../tools/r8/ir/conversion/IRBuilder.java | 1 + .../tools/r8/ir/conversion/IRConverter.java | 3 + .../r8/ir/conversion/LensCodeRewriter.java | 1 + .../r8/ir/conversion/StringSwitchRemover.java | 1 + .../ir/conversion/passes/BinopRewriter.java | 1 + .../conversion/passes/BranchSimplifier.java | 2 + .../CommonSubexpressionElimination.java | 1 + .../passes/DexConstantOptimizer.java | 1 + .../r8/ir/conversion/passes/SplitBranch.java | 1 + .../passes/ThrowCatchOptimizer.java | 1 + .../TrivialCheckCastAndInstanceOfRemover.java | 1 + .../r8/ir/optimize/AssertionsRewriter.java | 1 + .../tools/r8/ir/optimize/CodeRewriter.java | 3 + .../r8/ir/optimize/ConstantCanonicalizer.java | 1 + .../tools/r8/ir/optimize/DeadCodeRemover.java | 2 + .../tools/r8/ir/optimize/Devirtualizer.java | 1 + .../IdempotentFunctionCallCanonicalizer.java | 1 + .../android/tools/r8/ir/optimize/Inliner.java | 2 + .../r8/ir/optimize/NaturalIntLoopRemover.java | 1 + ...RedundantFieldLoadAndStoreElimination.java | 1 + .../r8/ir/optimize/ReflectionOptimizer.java | 1 + .../optimize/classinliner/ClassInliner.java | 1 + .../MemberValuePropagation.java | 1 + .../HorizontallyMergedClassInliningTest.java | 5 ++ .../tools/r8/ir/IrInjectionTestBase.java | 2 + .../android/tools/r8/ir/SplitBlockTest.java | 8 +-- .../r8/ir/optimize/NonNullTrackerTest.java | 1 + 30 files changed, 103 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index 9b3ea7c11f..a0dc9f6950 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java @@ -592,7 +592,19 @@ public boolean isConsistentSSA(AppView appView) { return true; } + public boolean isConsistentSSAAllowingRedundantBlocks(AppView appView) { + isConsistentSSABeforeTypesAreCorrectAllowingRedundantBlocks(appView); + assert verifyNoImpreciseOrBottomTypes(); + return true; + } + public boolean isConsistentSSABeforeTypesAreCorrect(AppView appView) { + assert isConsistentSSABeforeTypesAreCorrectAllowingRedundantBlocks(appView); + assert noRedundantBlocks(); + return true; + } + + public boolean isConsistentSSABeforeTypesAreCorrectAllowingRedundantBlocks(AppView appView) { assert isConsistentGraph(appView, true); assert consistentBlockInstructions(appView, true); assert consistentDefUseChains(); @@ -839,6 +851,13 @@ public boolean consistentBlockNumbering() { return true; } + private boolean noRedundantBlocks() { + for (BasicBlock block : blocks) { + assert !isRedundantBlock(block); + } + return true; + } + private boolean consistentBlockInstructions(AppView appView, boolean ssa) { boolean argumentsAllowed = true; for (BasicBlock block : blocks) { @@ -1253,6 +1272,42 @@ private boolean verifyAllThrowingInstructionsHavePositions() { return true; } + private boolean isRedundantBlock(BasicBlock block) { + return block.hasUniqueSuccessorWithUniquePredecessor() + && block.getInstructions().size() == 1 + && block.exit().isGoto() + && block.exit().getDebugValues().isEmpty() + && !block.isEntry(); + } + + public void removeRedundantBlocks() { + List blocksToRemove = new ArrayList<>(); + + for (BasicBlock block : blocks) { + // Check that there are no redundant blocks. + assert !blocksToRemove.contains(block); + if (isRedundantBlock(block)) { + assert block.getUniqueSuccessor().getMutablePredecessors().size() == 1; + assert block.getUniqueSuccessor().getMutablePredecessors().get(0) == block; + assert block.getUniqueSuccessor().getPhis().size() == 0; + // Let the successor consume this block. + BasicBlock successor = block.getUniqueSuccessor(); + successor.getMutablePredecessors().clear(); + successor.getMutablePredecessors().addAll(block.getPredecessors()); + successor.getPhis().addAll(block.getPhis()); + successor.getPhis().forEach(phi -> phi.setBlock(block.getUniqueSuccessor())); + block + .getPredecessors() + .forEach(predecessors -> predecessors.replaceSuccessor(block, successor)); + block.getMutablePredecessors().clear(); + block.getMutableSuccessors().clear(); + block.getPhis().clear(); + blocksToRemove.add(block); + } + } + blocks.removeAll(blocksToRemove); + } + public boolean removeAllDeadAndTrivialPhis() { return removeAllDeadAndTrivialPhis(null, null); } diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java index 7a3c7bed0e..049c51424e 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Phi.java +++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java @@ -39,7 +39,7 @@ public enum RegisterReadType { DEBUG, } - private final BasicBlock block; + private BasicBlock block; private final List operands = new ArrayList<>(); private RegisterReadType readType; private boolean isStackPhi; @@ -82,6 +82,10 @@ public BasicBlock getBlock() { return block; } + public void setBlock(BasicBlock block) { + this.block = block; + } + @Override public void constrainType( ValueTypeConstraint constraint, DexMethod method, Origin origin, Reporter reporter) { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java index 16fa00cb6c..ae68050f91 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java @@ -169,6 +169,7 @@ public CfCode build(DeadCodeRemover deadCodeRemover, Timing timing) { } timing.end(); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); // Insert reads for uninitialized read blocks to ensure correct stack maps. timing.begin("Insert uninitialized local reads"); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java index f297e8d8b7..0a5ebca7bb 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java @@ -746,6 +746,7 @@ public IRCode build(ProgramMethod context, MutableMethodConversionOptions conver StringSwitchConverter.convertToStringSwitchInstructions(ir, appView.dexItemFactory()); } + ir.removeRedundantBlocks(); assert ir.isConsistentSSA(appView); // Clear the code so we don't build multiple times. diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index bc82bc4011..387da18446 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -716,6 +716,7 @@ Timing optimize( .optimize(code, feedback, methodProcessor, methodProcessingContext); timing.end(); previous = printMethod(code, "IR after class library method optimizer (SSA)", previous); + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } @@ -850,6 +851,7 @@ Timing optimize( // always uses a force inlining oracle for inlining. -1))); timing.end(); + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); assert code.verifyTypes(appView); } @@ -936,6 +938,7 @@ Timing optimize( if (assumeInserter != null) { timing.begin("Remove assume instructions"); CodeRewriter.removeAssumeInstructions(appView, code); + code.removeRedundantBlocks(); timing.end(); assert code.isConsistentSSA(appView); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index 4c15125fa9..565fa1191f 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java @@ -854,6 +854,7 @@ public TypeElement getOutType() { } nullCheckInserter.processWorklist(); code.removeAllDeadAndTrivialPhis(); + code.removeRedundantBlocks(); removeUnusedArguments(method, code, unusedArguments); // Finalize cast and null check insertion. diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java index 8def4dbcee..dc2bcbddbb 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java @@ -97,6 +97,7 @@ public void run(IRCode code) { identifierNameStringMarker.decoupleIdentifierNameStringsInBlocks(code, newBlocksWithStrings); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java index a0edfc59af..fd1c7035ca 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java @@ -267,6 +267,7 @@ public void rewriteCode(ProgramMethod method, IRCode code) { } } code.removeAllDeadAndTrivialPhis(); + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index 199df6244e..28ddb0119a 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -124,6 +124,7 @@ public ControlFlowSimplificationResult simplifyIf(IRCode code) { if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified); } @@ -623,6 +624,7 @@ private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnal if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); return !affectedValues.isEmpty(); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java index 69c4e2060b..fb168cda3f 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java @@ -78,6 +78,7 @@ && shareCatchHandlers(instruction, candidate.definition)) { } } code.returnMarkingColor(noCandidate); + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java index 1e00a5e51c..d170c0b25e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java @@ -316,6 +316,7 @@ public void shortenLiveRanges(IRCode code, ConstantCanonicalizer canonicalizer) } } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java index 7e17e7eeba..8da51196da 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java @@ -73,6 +73,7 @@ void rewriteCode(ProgramMethod method, IRCode code) { if (ALLOW_PARTIAL_REWRITE) { code.splitCriticalEdges(); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java index 8b170c5900..f34a6bd20e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java @@ -313,6 +313,7 @@ public void optimizeAlwaysThrowingInstructions(IRCode code) { if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java index e19b9633dd..bd09aaef54 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java @@ -131,6 +131,7 @@ public void run( typeAnalysis.narrowing(affectedValues); } } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java index 1961b30b89..e1f8d9b1d1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java @@ -347,6 +347,7 @@ public void run( if (runInternal(method, code)) { deadCodeRemover.run(code, timing); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); timing.end(); } 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 af0023f357..80450b0e9c 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 @@ -241,6 +241,7 @@ public boolean rewriteMoveResult(IRCode code) { if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); return changed; } @@ -294,6 +295,7 @@ public void splitRangeInvokeConstants(IRCode code) { } } } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } @@ -341,6 +343,7 @@ public void rewriteKnownArrayLengthCalls(IRCode code) { phiUsers.forEach(Phi::removeTrivialPhi); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java index b4fa5a65e4..c7ddc680e5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java @@ -291,6 +291,7 @@ public boolean equals(Instruction a, Instruction b) { branchSimplifier.simplifyIf(code); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); return clear(); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index fcefb79b1f..a2074860e1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java @@ -67,6 +67,8 @@ public void run(IRCode code, Timing timing) { } } while (branchSimplifier.simplifyIf(code).anySimplifications() || removeUnneededCatchHandlers(code)); + + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); timing.end(); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java index f95b59e829..9a086cd9f9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java @@ -315,6 +315,7 @@ public void devirtualizeInvokeInterface(IRCode code) { if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java index 7aae8a5e1a..c7b3cfd7a0 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java @@ -228,6 +228,7 @@ public boolean equals(InvokeMethod a, InvokeMethod b) { } code.removeAllDeadAndTrivialPhis(); + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java index 0d20bc391b..11d016e452 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java @@ -705,6 +705,7 @@ InlineeWithReason buildInliningIR( if (options.testing.inlineeIrModifier != null) { options.testing.inlineeIrModifier.accept(code); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); return new InlineeWithReason(code, reason); } @@ -1098,6 +1099,7 @@ private void performInliningImpl( classInitializationAnalysis.finish(); code.removeBlocks(blocksToRemove); code.removeAllDeadAndTrivialPhis(); + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java index e55a1b08ab..fc264213a7 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java @@ -41,6 +41,7 @@ public void run(AppView appView, IRCode code) { } if (loopRemoved) { code.removeAllDeadAndTrivialPhis(); + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java index 06a8cc2b04..1ea3e94ab3 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java @@ -444,6 +444,7 @@ public void run() { } processInstructionsToRemove(); assumeRemover.removeMarkedInstructions().finish(); + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java index be176f202e..d7d41bde65 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java @@ -74,6 +74,7 @@ public static void rewriteGetClassOrForNameToConstClass( if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index 889a43448c..05a01c0384 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -228,6 +228,7 @@ public final void processMethodCode( // Restore normality. assumeRemover.removeMarkedInstructions(); code.removeAllDeadAndTrivialPhis(affectedValues); + code.removeRedundantBlocks(); assumeRemover.finish(); assert code.isConsistentSSA(appView); rootsIterator.remove(); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java index 4d89225546..90fd365d7a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java @@ -65,6 +65,7 @@ public void run(IRCode code) { if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); assert code.verifyTypes(appView); } diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontallyMergedClassInliningTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontallyMergedClassInliningTest.java index 3874037b82..48df732a7e 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontallyMergedClassInliningTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontallyMergedClassInliningTest.java @@ -37,6 +37,11 @@ public void test() throws Exception { inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class)) .allowAccessModification() .setMinApi(parameters) + .addOptionsModification( + options -> { + // With removal of redundant blocks this heuristic needs to be raised. + options.classInlinerOptions().classInliningInstructionAllowance = 66; + }) .compile() .inspect( inspector -> { diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java index 25fc777b5b..3143cb0605 100644 --- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java +++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java @@ -69,6 +69,7 @@ public TestApplication(AppView appView, MethodSubject method, List ad this.appView = appView; this.method = method.getMethod(); this.code = method.buildIR(); + code.removeRedundantBlocks(); this.additionalCode = additionalCode; this.consumers = new AndroidAppConsumers(appView.options()); int largestValueNumber = -1; @@ -118,6 +119,7 @@ private AndroidApp writeDex() { public String run() throws IOException { Timing timing = Timing.empty(); IRConverter converter = new IRConverter(appView); + code.removeRedundantBlocks(); converter.replaceCodeForTesting(code); AndroidApp app = writeDex(); return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout; diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java index 66adde9b6c..484aca5e11 100644 --- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java +++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java @@ -206,7 +206,7 @@ private void hasCatchandlerIfThrowing(BasicBlock block) { private void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) throws Exception { final int secondBlockInstructions = 4; - final int initialBlockCount = twoGuards ? 7 : 6; + final int initialBlockCount = twoGuards ? 7 : 5; // Try split between all instructions in second block. for (int i = 1; i < secondBlockInstructions; i++) { TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards); @@ -220,7 +220,7 @@ private void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) throws E InstructionListIterator iterator = test.listIteratorAt(block, i); BasicBlock newBlock = iterator.split(code); - assertTrue(code.isConsistentSSA(appView)); + assertTrue(code.isConsistentSSAAllowingRedundantBlocks(appView)); assertEquals(initialBlockCount + 1, code.blocks.size()); assertEquals(i + 1, code.blocks.get(1).getInstructions().size()); @@ -246,7 +246,7 @@ public void catchHandlers() throws Exception { private void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards) throws Exception { final int secondBlockInstructions = 4; - final int initialBlockCount = twoGuards ? 7 : 6; + final int initialBlockCount = twoGuards ? 7 : 5; // Try split out all instructions in second block. for (int i = 1; i < secondBlockInstructions - 1; i++) { TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards); @@ -260,7 +260,7 @@ private void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards InstructionListIterator iterator = test.listIteratorAt(block, i); BasicBlock newBlock = iterator.split(code, 1); - assertTrue(code.isConsistentSSA(appView)); + assertTrue(code.isConsistentSSAAllowingRedundantBlocks(appView)); assertEquals(initialBlockCount + 2, code.blocks.size()); assertEquals(i + 1, code.blocks.get(1).getInstructions().size()); diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java index 9d6d411ee6..f04152626c 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java @@ -73,6 +73,7 @@ private void buildAndTest( } CodeRewriter.removeAssumeInstructions(appView, code); + code.removeRedundantBlocks(); assertTrue(code.isConsistentSSA(appView)); checkCountOfNonNull(code, 0); } From 1acb505122e5ae79bbbacedddc355f4de055c1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Tue, 13 Jun 2023 10:36:23 +0200 Subject: [PATCH 083/153] Remove SplitRangeInvoke - worsen code size on Tivi - run before constant canonicalization Change-Id: I9d22745f94da6db1e9658c39dec6649bd3e1a994 --- .../tools/r8/ir/conversion/IRConverter.java | 3 -- .../tools/r8/ir/optimize/CodeRewriter.java | 44 ------------------- 2 files changed, 47 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 387da18446..9e5d496726 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -766,9 +766,6 @@ Timing optimize( StringBuilderAppendOptimizer.run(appView, code); timing.end(); } - timing.begin("Split range invokes"); - codeRewriter.splitRangeInvokeConstants(code); - timing.end(); timing.begin("Propagate sparse conditionals"); new SparseConditionalConstantPropagation(appView, code).run(); timing.end(); 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 80450b0e9c..0476582092 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 @@ -36,7 +36,6 @@ import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; -import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeInterface; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeVirtual; @@ -64,10 +63,8 @@ import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; import java.util.ArrayList; import java.util.Collections; -import java.util.IdentityHashMap; import java.util.List; import java.util.ListIterator; -import java.util.Map; import java.util.Set; public class CodeRewriter { @@ -262,43 +259,6 @@ public static void removeOrReplaceByDebugLocalWrite( } } - // Split constants that flow into ranged invokes. This gives the register allocator more - // freedom in assigning register to ranged invokes which can greatly reduce the number - // of register needed (and thereby code size as well). - public void splitRangeInvokeConstants(IRCode code) { - for (BasicBlock block : code.blocks) { - InstructionListIterator it = block.listIterator(code); - while (it.hasNext()) { - Instruction current = it.next(); - if (current.isInvoke() && current.asInvoke().requiredArgumentRegisters() > 5) { - Invoke invoke = current.asInvoke(); - it.previous(); - Map oldToNew = new IdentityHashMap<>(); - for (int i = 0; i < invoke.inValues().size(); i++) { - Value value = invoke.inValues().get(i); - if (value.isConstNumber() && value.numberOfUsers() > 1) { - ConstNumber definition = value.getConstInstruction().asConstNumber(); - Value originalValue = definition.outValue(); - ConstNumber newNumber = oldToNew.get(definition); - if (newNumber == null) { - newNumber = ConstNumber.copyOf(code, definition); - it.add(newNumber); - newNumber.setPosition(current.getPosition()); - oldToNew.put(definition, newNumber); - } - invoke.inValues().set(i, newNumber.outValue()); - originalValue.removeUser(invoke); - newNumber.outValue().addUser(invoke); - } - } - it.next(); - } - } - } - code.removeRedundantBlocks(); - assert code.isConsistentSSA(appView); - } - public void rewriteKnownArrayLengthCalls(IRCode code) { InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) { @@ -347,10 +307,6 @@ public void rewriteKnownArrayLengthCalls(IRCode code) { assert code.isConsistentSSA(appView); } - - - - // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block. private static boolean hasLocalOrLineChangeBetween( Instruction from, Instruction to, DexString localVar) { From 1af3747ae7a1fa1a932fa0014ee05ce911faa7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Tue, 13 Jun 2023 10:37:31 +0200 Subject: [PATCH 084/153] Make NaturalIntLoopRemover a CodeRewriterPass Change-Id: I82ba6746022c8c3ce460bc290cb0ee4eac62465a --- .../tools/r8/ir/conversion/IRConverter.java | 7 ++--- .../passes}/NaturalIntLoopRemover.java | 28 ++++++++++++++----- 2 files changed, 23 insertions(+), 12 deletions(-) rename src/main/java/com/android/tools/r8/ir/{optimize => conversion/passes}/NaturalIntLoopRemover.java (95%) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 9e5d496726..7aae7017f4 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -32,6 +32,7 @@ import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer; +import com.android.tools.r8.ir.conversion.passes.NaturalIntLoopRemover; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer; @@ -53,7 +54,6 @@ import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; -import com.android.tools.r8.ir.optimize.NaturalIntLoopRemover; import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination; import com.android.tools.r8.ir.optimize.ReflectionOptimizer; import com.android.tools.r8.ir.optimize.RemoveVerificationErrorForUnknownReturnedValues; @@ -123,7 +123,6 @@ public class IRConverter { public final CommonSubexpressionElimination commonSubexpressionElimination; private final SplitBranch splitBranch; public AssertionErrorTwoArgsConstructorRewriter assertionErrorTwoArgsConstructorRewriter; - private final NaturalIntLoopRemover naturalIntLoopRemover = new NaturalIntLoopRemover(); public final MemberValuePropagation memberValuePropagation; private final LensCodeRewriter lensCodeRewriter; protected final Inliner inliner; @@ -747,9 +746,7 @@ Timing optimize( timing.begin("Rewrite array length"); codeRewriter.rewriteKnownArrayLengthCalls(code); timing.end(); - timing.begin("Natural Int Loop Remover"); - naturalIntLoopRemover.run(appView, code); - timing.end(); + new NaturalIntLoopRemover(appView).run(context, code); if (assertionErrorTwoArgsConstructorRewriter != null) { timing.begin("Rewrite AssertionError"); assertionErrorTwoArgsConstructorRewriter.rewrite( diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java similarity index 95% rename from src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java rename to src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java index fc264213a7..16e9415ba0 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java @@ -1,11 +1,13 @@ -// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// Copyright (c) 2023, 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.ir.optimize; +package com.android.tools.r8.ir.conversion.passes; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; @@ -27,12 +29,19 @@ * pattern match fori and for loops with any initial value and increment, but this should be * extended for while loop support. */ -public class NaturalIntLoopRemover { +public class NaturalIntLoopRemover extends CodeRewriterPass { - public void run(AppView appView, IRCode code) { - if (!appView.options().enableLoopUnrolling) { - return; - } + public NaturalIntLoopRemover(AppView appView) { + super(appView); + } + + @Override + String getTimingId() { + return "NaturalIntLoopRemover"; + } + + @Override + void rewriteCode(ProgramMethod method, IRCode code) { boolean loopRemoved = false; for (BasicBlock comparisonBlockCandidate : code.blocks) { if (isComparisonBlock(comparisonBlockCandidate)) { @@ -46,6 +55,11 @@ public void run(AppView appView, IRCode code) { } } + @Override + boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + return appView.options().enableLoopUnrolling; + } + private boolean isComparisonBlock(BasicBlock comparisonBlockCandidate) { if (!comparisonBlockCandidate.exit().isIf() || comparisonBlockCandidate.exit().asIf().isZeroTest()) { From 9250090e12d68aec0d83e21990bb95c6c47d7645 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 13 Jun 2023 08:15:17 +0200 Subject: [PATCH 085/153] [Retrace] Move promise test out Change-Id: I5a52795f7fb4a0d607c08e3388a8cb0e5804bafa --- .../retrace/api/RetraceApiTypeResultTest.java | 191 --------------- ...RetracePartitionStackTracePromiseTest.java | 220 ++++++++++++++++++ .../retrace/binary_compatibility.tar.gz.sha1 | 2 +- 3 files changed, 221 insertions(+), 192 deletions(-) create mode 100644 src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStackTracePromiseTest.java diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java index 72a17ac4d3..e8254ff569 100644 --- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java +++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java @@ -13,7 +13,6 @@ import com.android.tools.r8.TestDiagnosticMessagesImpl; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; -import com.android.tools.r8.ThrowingFunction; import com.android.tools.r8.naming.retrace.StackTrace; import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; import com.android.tools.r8.references.ClassReference; @@ -21,24 +20,18 @@ import com.android.tools.r8.references.TypeReference; import com.android.tools.r8.retrace.MappingPartitionMetadata; import com.android.tools.r8.retrace.PartitionMappingSupplier; -import com.android.tools.r8.retrace.PartitionMappingSupplierAsync; import com.android.tools.r8.retrace.ProguardMapPartitioner; import com.android.tools.r8.retrace.ProguardMapProducer; import com.android.tools.r8.retrace.Retrace; -import com.android.tools.r8.retrace.RetraceAsync; -import com.android.tools.r8.retrace.RetraceAsyncResult; import com.android.tools.r8.retrace.RetraceStackTraceContext; import com.android.tools.r8.retrace.RetraceStackTraceElementProxy; -import com.android.tools.r8.retrace.RetraceStackTraceResult; import com.android.tools.r8.retrace.RetraceTypeElement; import com.android.tools.r8.retrace.RetracedMethodReference; import com.android.tools.r8.retrace.RetracedMethodReference.KnownRetracedMethodReference; import com.android.tools.r8.retrace.Retracer; import com.android.tools.r8.retrace.StackTraceElementProxy; import com.android.tools.r8.retrace.StackTraceLineParser; -import com.android.tools.r8.retrace.api.RetraceApiTypeResultTest.RetracePartitionStackTraceTest.IdentityStackTraceLineParser; import com.android.tools.r8.utils.BooleanBox; -import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.StringUtils; import java.util.ArrayList; import java.util.HashMap; @@ -99,190 +92,6 @@ public void testRetracePrimitiveArray() { } } - @RunWith(Parameterized.class) - public static class RetracePartitionStackTracePromiseTest extends TestBase { - - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withNoneRuntime().build(); - } - - public RetracePartitionStackTracePromiseTest(TestParameters parameters) { - parameters.assertNoneRuntime(); - } - - private final String MAPPING = - StringUtils.unixLines( - "com.Foo -> a:", - " 1:1:void m1():42:42 -> a", - "com.Bar -> b:", - " 2:2:void m2():43:43 -> b", - "com.Baz -> c:", - " 3:3:void m3():44:44 -> c"); - - public static class Promise { - - private final T value; - - private Promise(T value) { - this.value = value; - } - - public Promise then(ThrowingFunction f) - throws InterruptedException { - return new Promise<>(f.apply(value)); - } - - public Promise thenApply(ThrowingFunction, InterruptedException> f) - throws InterruptedException { - return f.apply(value); - } - } - - public static class PromiseRunner { - - public T run(Promise promise) { - // Here we are cheating since we know the value is always available. - return promise.value; - } - } - - public static class Storage { - - private final byte[] metadata; - private final Map partitions; - - public Storage(byte[] metadata, Map partitions) { - this.metadata = metadata; - this.partitions = partitions; - } - - public Promise getMetadata() { - return new Promise<>(metadata); - } - - public Promise getPartition(String key) { - return new Promise<>(partitions.getOrDefault(key, new byte[0])); - } - } - - private Storage buildStorage(TestDiagnosticMessagesImpl diagnosticMessages) throws Exception { - Map partitions = new HashMap<>(); - MappingPartitionMetadata metadataPromise = - ProguardMapPartitioner.builder(diagnosticMessages) - .setProguardMapProducer(ProguardMapProducer.fromString(MAPPING)) - .setPartitionConsumer( - partition -> partitions.put(partition.getKey(), partition.getPayload())) - .build() - .run(); - return new Storage(metadataPromise.getBytes(), partitions); - } - - private static Promise> transpose(Map> promiseMap) - throws InterruptedException { - Map resolvedMap = new HashMap<>(); - Box interruptedException = new Box<>(); - promiseMap.forEach( - (key, promise) -> { - try { - promise.then(resolvedValue -> resolvedMap.put(key, resolvedValue)); - } catch (InterruptedException e) { - interruptedException.set(e); - } - }); - if (interruptedException.isSet()) { - throw interruptedException.get(); - } - return new Promise<>(resolvedMap); - } - - @Test - public void testRetrace() throws Exception { - TestDiagnosticMessagesImpl diagnosticMessages = new TestDiagnosticMessagesImpl(); - Storage storage = buildStorage(diagnosticMessages); - - List minifiedStackTrace = new ArrayList<>(); - minifiedStackTrace.add(StackTraceLine.parse("at a.a(SourceFile:1)")); - minifiedStackTrace.add(StackTraceLine.parse("at b.b(SourceFile:2)")); - minifiedStackTrace.add(StackTraceLine.parse("at c.c(SourceFile:3)")); - - StackTrace retracedStacktrace = - new PromiseRunner() - .run( - storage - .getMetadata() - .thenApply( - metadata -> { - Map> partitionRequests = new HashMap<>(); - RetraceAsyncResult> - asyncResult = - RetraceAsync - . - builder() - .setStackTraceLineParser(new IdentityStackTraceLineParser()) - .setDiagnosticsHandler(diagnosticMessages) - .setMappingSupplier( - PartitionMappingSupplierAsync.builder() - .setMetadata(metadata) - .setRegisterMappingPartitionCallback( - key -> - partitionRequests.put( - key, storage.getPartition(key))) - .build()) - .build() - .retraceStackTrace( - minifiedStackTrace, RetraceStackTraceContext.empty()); - return getThen(partitionRequests, asyncResult); - })); - StackTrace expectedStackTrace = - StackTrace.builder() - .add( - StackTraceLine.builder() - .setClassName("com.Foo") - .setMethodName("m1") - .setFileName("Foo.java") - .setLineNumber(42) - .build()) - .add( - StackTraceLine.builder() - .setClassName("com.Bar") - .setMethodName("m2") - .setFileName("Bar.java") - .setLineNumber(43) - .build()) - .add( - StackTraceLine.builder() - .setClassName("com.Baz") - .setMethodName("m3") - .setFileName("Baz.java") - .setLineNumber(44) - .build()) - .build(); - assertThat(retracedStacktrace, isSame(expectedStackTrace)); - } - - // This method needs to be outlined due to a bug in javac 8. - private Promise getThen( - Map> partitionRequests, - RetraceAsyncResult> asyncResult) - throws InterruptedException { - return transpose(partitionRequests) - .then( - resolvedPartitions -> { - StackTrace.Builder retraceStackTraceBuilder = StackTrace.builder(); - asyncResult - .getResult(resolvedPartitions::get) - .forEach( - retraced -> { - Assert.assertEquals(1, retraced.size()); - retraced.get(0).forEach(retraceStackTraceBuilder::add); - }); - return retraceStackTraceBuilder.build(); - }); - } - } - @RunWith(Parameterized.class) public static class RetracePartitionStackTraceTest extends TestBase { diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStackTracePromiseTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStackTracePromiseTest.java new file mode 100644 index 0000000000..3515d32ba5 --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStackTracePromiseTest.java @@ -0,0 +1,220 @@ +// Copyright (c) 2023, 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.retrace.api; + +import static com.android.tools.r8.naming.retrace.StackTrace.isSame; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessagesImpl; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ThrowingFunction; +import com.android.tools.r8.naming.retrace.StackTrace; +import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; +import com.android.tools.r8.retrace.MappingPartitionMetadata; +import com.android.tools.r8.retrace.PartitionMappingSupplierAsync; +import com.android.tools.r8.retrace.ProguardMapPartitioner; +import com.android.tools.r8.retrace.ProguardMapProducer; +import com.android.tools.r8.retrace.RetraceAsync; +import com.android.tools.r8.retrace.RetraceAsyncResult; +import com.android.tools.r8.retrace.RetraceStackTraceContext; +import com.android.tools.r8.retrace.RetraceStackTraceResult; +import com.android.tools.r8.retrace.api.RetraceApiTypeResultTest.RetracePartitionStackTraceTest; +import com.android.tools.r8.retrace.api.RetraceApiTypeResultTest.RetracePartitionStackTraceTest.IdentityStackTraceLineParser; +import com.android.tools.r8.utils.Box; +import com.android.tools.r8.utils.StringUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class RetracePartitionStackTracePromiseTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public RetracePartitionStackTracePromiseTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + private final String MAPPING = + StringUtils.unixLines( + "com.Foo -> a:", + " 1:1:void m1():42:42 -> a", + "com.Bar -> b:", + " 2:2:void m2():43:43 -> b", + "com.Baz -> c:", + " 3:3:void m3():44:44 -> c"); + + public static class Promise { + + private final T value; + + private Promise(T value) { + this.value = value; + } + + public Promise then(ThrowingFunction f) + throws InterruptedException { + return new Promise<>(f.apply(value)); + } + + public Promise thenApply(ThrowingFunction, InterruptedException> f) + throws InterruptedException { + return f.apply(value); + } + } + + public static class PromiseRunner { + + public T run(Promise promise) { + // Here we are cheating since we know the value is always available. + return promise.value; + } + } + + public static class Storage { + + private final byte[] metadata; + private final Map partitions; + + public Storage(byte[] metadata, Map partitions) { + this.metadata = metadata; + this.partitions = partitions; + } + + public Promise getMetadata() { + return new Promise<>(metadata); + } + + public Promise getPartition(String key) { + return new Promise<>(partitions.getOrDefault(key, new byte[0])); + } + } + + private Storage buildStorage(TestDiagnosticMessagesImpl diagnosticMessages) throws Exception { + Map partitions = new HashMap<>(); + MappingPartitionMetadata metadataPromise = + ProguardMapPartitioner.builder(diagnosticMessages) + .setProguardMapProducer(ProguardMapProducer.fromString(MAPPING)) + .setPartitionConsumer( + partition -> partitions.put(partition.getKey(), partition.getPayload())) + .build() + .run(); + return new Storage(metadataPromise.getBytes(), partitions); + } + + private static Promise> transpose(Map> promiseMap) + throws InterruptedException { + Map resolvedMap = new HashMap<>(); + Box interruptedException = new Box<>(); + promiseMap.forEach( + (key, promise) -> { + try { + promise.then(resolvedValue -> resolvedMap.put(key, resolvedValue)); + } catch (InterruptedException e) { + interruptedException.set(e); + } + }); + if (interruptedException.isSet()) { + throw interruptedException.get(); + } + return new Promise<>(resolvedMap); + } + + @Test + public void testRetrace() throws Exception { + TestDiagnosticMessagesImpl diagnosticMessages = new TestDiagnosticMessagesImpl(); + Storage storage = buildStorage(diagnosticMessages); + + List minifiedStackTrace = new ArrayList<>(); + minifiedStackTrace.add(StackTraceLine.parse("at a.a(SourceFile:1)")); + minifiedStackTrace.add(StackTraceLine.parse("at b.b(SourceFile:2)")); + minifiedStackTrace.add(StackTraceLine.parse("at c.c(SourceFile:3)")); + + StackTrace retracedStacktrace = + new PromiseRunner() + .run( + storage + .getMetadata() + .thenApply( + metadata -> { + Map> partitionRequests = new HashMap<>(); + RetraceAsyncResult> asyncResult = + RetraceAsync + . + builder() + .setStackTraceLineParser(new IdentityStackTraceLineParser()) + .setDiagnosticsHandler(diagnosticMessages) + .setMappingSupplier( + PartitionMappingSupplierAsync.builder() + .setMetadata(metadata) + .setRegisterMappingPartitionCallback( + key -> + partitionRequests.put( + key, storage.getPartition(key))) + .build()) + .build() + .retraceStackTrace( + minifiedStackTrace, RetraceStackTraceContext.empty()); + return getThen(partitionRequests, asyncResult); + })); + StackTrace expectedStackTrace = + StackTrace.builder() + .add( + StackTraceLine.builder() + .setClassName("com.Foo") + .setMethodName("m1") + .setFileName("Foo.java") + .setLineNumber(42) + .build()) + .add( + StackTraceLine.builder() + .setClassName("com.Bar") + .setMethodName("m2") + .setFileName("Bar.java") + .setLineNumber(43) + .build()) + .add( + StackTraceLine.builder() + .setClassName("com.Baz") + .setMethodName("m3") + .setFileName("Baz.java") + .setLineNumber(44) + .build()) + .build(); + assertThat(retracedStacktrace, isSame(expectedStackTrace)); + } + + // This method needs to be outlined due to a bug in javac 8. + private Promise getThen( + Map> partitionRequests, + RetraceAsyncResult> asyncResult) + throws InterruptedException { + return transpose(partitionRequests) + .then( + resolvedPartitions -> { + StackTrace.Builder retraceStackTraceBuilder = StackTrace.builder(); + asyncResult + .getResult(resolvedPartitions::get) + .forEach( + retraced -> { + Assert.assertEquals(1, retraced.size()); + retraced.get(0).forEach(retraceStackTraceBuilder::add); + }); + return retraceStackTraceBuilder.build(); + }); + } +} diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1 index 4659358ed9..c82ea4f97c 100644 --- a/third_party/retrace/binary_compatibility.tar.gz.sha1 +++ b/third_party/retrace/binary_compatibility.tar.gz.sha1 @@ -1 +1 @@ -cc086e35ef1bb4754cbfa8498af00dc5b9e43002 \ No newline at end of file +a6004e441b5237c3b5436b64011eac6552e68faa \ No newline at end of file From ce359de342a36c149679a40499fffa89dfb1184b Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Mon, 12 Jun 2023 18:19:53 +0200 Subject: [PATCH 086/153] [Compose] Add test for error when mapping outline positions Bug: b/286781273 Change-Id: I62e6202c9697efd5ea7007a1a1d485caddf56206 --- .../ComposeOutlineWithRangeTest.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithRangeTest.java diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithRangeTest.java new file mode 100644 index 0000000000..ccc02d2b5f --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithRangeTest.java @@ -0,0 +1,62 @@ +// Copyright (c) 2023, 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.mappingcompose; + +import static org.junit.Assert.assertThrows; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposeException; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** This is a regression test for b/286781273. */ +@RunWith(Parameterized.class) +public class ComposeOutlineWithRangeTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# { id: 'com.android.tools.r8.mapping', version: '2.2' }", + "outline.Class -> a:", + " 1:2:int some.inlinee():75:76 -> a", + " 1:2:int outline():0 -> a", + " # { 'id':'com.android.tools.r8.outline' }", + "outline.Callsite -> x:", + " 1:1:int outlineCaller(int):0:0 -> s", + " # { 'id':'com.android.tools.r8.outlineCallsite'," + + "'positions': { '1': 10, '2': 11 }," + + "'outline':'La;a()I' }", + " 10:11:int outlineCaller(int):23:24 -> s"); + private static final String mappingBar = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "a -> b:", + " 4:5:int a():1:2 -> m", + "x -> y:", + " 42:42:int s(int):1:1 -> o"); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar); + // TODO(b/286781273): We should not throw an exception. + assertThrows( + MappingComposeException.class, () -> MappingComposer.compose(mappingForFoo, mappingForBar)); + } +} From 9f93c67b0f9815f0d34a58130706524189048cb8 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 13 Jun 2023 11:22:24 +0200 Subject: [PATCH 087/153] [Compose] Lookup prev/curr interval for computing new outline positions Bug: b/286781273 Change-Id: Id8b9d73b95b284d9c815621b0ff60d49b8c88821 --- .../tools/r8/naming/ComposingBuilder.java | 70 ++++++++++++++----- .../r8/mappingcompose/ComposeOutlineTest.java | 4 +- .../ComposeOutlineWithRangeTest.java | 23 ++++-- 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index 0ccde4760f..f21fafe789 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -49,7 +49,6 @@ import java.util.Objects; import java.util.Set; import java.util.TreeMap; -import java.util.function.BiConsumer; import java.util.function.Consumer; public class ComposingBuilder { @@ -738,7 +737,9 @@ private List fixupInlinedOutlines( visitOutlineMappedPositions( outlineCallSiteInformation, computedInformationForCallSite.current.getOriginalSignature(), - mappedRangesForOutline::put); + positionInfo -> + mappedRangesForOutline.put( + positionInfo.outlinePosition(), positionInfo.mappedRanges())); List newComposedRanges = new ArrayList<>(); // Copy all previous handled mapped ranges into a new list. for (MappedRange previousMappedRanges : composedRanges) { @@ -866,33 +867,54 @@ private void fixupOutlineCallsiteInformation( visitOutlineMappedPositions( outlineCallSite, originalSignature, - (originalPosition, mappedRangesForOutlinePosition) -> { + positionInfo -> { int newIndex = firstAvailableRange.getAndIncrement(); Range newMinifiedRange = new Range(newIndex, newIndex); - MappedRange outerMostOutlineFrame = ListUtils.last(mappedRangesForOutlinePosition); - for (MappedRange inlineMappedRangeInOutlinePosition : - mappedRangesForOutlinePosition) { - if (inlineMappedRangeInOutlinePosition != outerMostOutlineFrame) { + MappedRange outerMostOutlineCallsiteFrame = + ListUtils.last(positionInfo.mappedRanges()); + for (MappedRange inlineMappedRangeInOutlinePosition : positionInfo.mappedRanges()) { + if (inlineMappedRangeInOutlinePosition != outerMostOutlineCallsiteFrame) { composedRanges.add( inlineMappedRangeInOutlinePosition.withMinifiedRange(newMinifiedRange)); } } + int originalPosition = + outerMostOutlineCallsiteFrame.getOriginalLineNumber( + positionInfo.outlineCallsitePosition()); + boolean hasInlineFrames = positionInfo.mappedRanges().size() > 1; composedRanges.add( new MappedRange( newMinifiedRange, lastComposedRange.signature, - outerMostOutlineFrame.originalRange, + hasInlineFrames + ? new Range(originalPosition) + : new Range(originalPosition, originalPosition), lastComposedRange.getRenamedName())); - newPositionMap.put((int) originalPosition, newIndex); + newPositionMap.put(positionInfo.outlinePosition(), newIndex); outlineCallSite.setPositionsInternal(newPositionMap); }); } } + /** + * The class contains a list of mapped ranges for a single outline position. + * + *

Outline positions is a key-value map of positions in the outline to positions in the + * callsite. An instance of OutlineCallSitePositionWithMappedRanges contains the information + * about a single position. + */ + public interface OutlineCallSitePositionWithMappedRanges { + int outlinePosition(); + + int outlineCallsitePosition(); + + List mappedRanges(); + } + private void visitOutlineMappedPositions( OutlineCallsiteMappingInformation outlineCallSite, MethodSignature originalSignature, - BiConsumer> outlinePositionConsumer) + Consumer outlinePositionConsumer) throws MappingComposeException { Int2IntSortedMap positionMap = outlineCallSite.getPositions(); ComposingClassBuilder existingClassBuilder = getExistingClassBuilder(originalSignature); @@ -908,9 +930,9 @@ private void visitOutlineMappedPositions( "Could not find method positions for original signature '" + originalSignature + "'."); } for (Integer keyPosition : positionMap.keySet()) { - int keyPositionInt = keyPosition; - int originalDestination = positionMap.get(keyPositionInt); - List mappedRanges = outlineSegmentTree.find(originalDestination); + int outlinePosition = keyPosition; + int callsitePosition = positionMap.get(outlinePosition); + List mappedRanges = outlineSegmentTree.find(callsitePosition); if (mappedRanges == null) { throw new MappingComposeException( "Could not find ranges for outline position '" @@ -921,10 +943,10 @@ private void visitOutlineMappedPositions( } ExistingMappings existingMappings = ExistingMappings.create(mappedRanges); List mappedRangesForOutlinePosition = - existingMappings.getNextRanges(originalDestination); + existingMappings.getPreviousRanges(callsitePosition); if (mappedRangesForOutlinePosition == null || mappedRangesForOutlinePosition.isEmpty() - || !mappedRangesForOutlinePosition.get(0).minifiedRange.contains(originalDestination)) { + || !mappedRangesForOutlinePosition.get(0).minifiedRange.contains(callsitePosition)) { throw new MappingComposeException( "Could not find ranges for outline position '" + keyPosition @@ -932,7 +954,23 @@ private void visitOutlineMappedPositions( + originalSignature + "'."); } - outlinePositionConsumer.accept(keyPositionInt, mappedRangesForOutlinePosition); + outlinePositionConsumer.accept( + new OutlineCallSitePositionWithMappedRanges() { + @Override + public int outlinePosition() { + return outlinePosition; + } + + @Override + public int outlineCallsitePosition() { + return callsitePosition; + } + + @Override + public List mappedRanges() { + return mappedRangesForOutlinePosition; + } + }); } } diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java index f722cc3fa8..48bfdfd299 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java @@ -37,7 +37,7 @@ public static TestParametersCollection data() { " 1:2:int outline():0 -> a", " # { 'id':'com.android.tools.r8.outline' }", "outline.Callsite -> x:", - " 4:4:int outlineCaller(int):23 -> s", + " 4:4:int outlineCaller(int):23:23 -> s", " 5:5:int foo.bar.baz.outlineCaller(int):98:98 -> s", " 5:5:int outlineCaller(int):24 -> s", " 27:27:int outlineCaller(int):0:0 -> s", @@ -61,7 +61,7 @@ public static TestParametersCollection data() { " # {'id':'com.android.tools.r8.outlineCallsite'," + "'positions':{'4':43,'5':44}," + "'outline':'Lc;m()I'}", - " 43:43:int outlineCaller(int):23 -> o", + " 43:43:int outlineCaller(int):23:23 -> o", " 44:44:int foo.bar.baz.outlineCaller(int):98:98 -> s", " 44:44:int outlineCaller(int):24 -> o", "outline.Class -> c:", diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithRangeTest.java index ccc02d2b5f..1e846e1bc5 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithRangeTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithRangeTest.java @@ -4,13 +4,13 @@ package com.android.tools.r8.mappingcompose; -import static org.junit.Assert.assertThrows; +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.naming.ClassNameMapper; -import com.android.tools.r8.naming.MappingComposeException; import com.android.tools.r8.naming.MappingComposer; import com.android.tools.r8.utils.StringUtils; import org.junit.Test; @@ -50,13 +50,26 @@ public static TestParametersCollection data() { " 4:5:int a():1:2 -> m", "x -> y:", " 42:42:int s(int):1:1 -> o"); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "outline.Callsite -> y:", + " 42:42:int outlineCaller(int):0:0 -> o", + " # {'id':'com.android.tools.r8.outlineCallsite'," + + "'positions':{'4':43,'5':44}," + + "'outline':'Lb;m()I'}", + " 43:43:int outlineCaller(int):23:23 -> o", + " 44:44:int outlineCaller(int):24:24 -> o", + "outline.Class -> b:", + " 4:5:int some.inlinee():75:76 -> m", + " 4:5:int outline():0 -> m", + " # {'id':'com.android.tools.r8.outline'}"); @Test public void testCompose() throws Exception { ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo); ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar); - // TODO(b/286781273): We should not throw an exception. - assertThrows( - MappingComposeException.class, () -> MappingComposer.compose(mappingForFoo, mappingForBar)); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); } } From 90ae39d57c577fe213089c996b02271d39a872af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Tue, 13 Jun 2023 14:31:58 +0200 Subject: [PATCH 088/153] Add redundant blocks removal - fix red bots Change-Id: I507c0672f04f06db417e904c50200a93826bfca0 --- .../analysis/constant/SparseConditionalConstantPropagation.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java index 69ef71cd9e..e6a6d31b56 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java @@ -130,6 +130,7 @@ private void rewriteCode() { new TypeAnalysis(appView).narrowing(affectedValues); } code.removeAllDeadAndTrivialPhis(); + code.removeRedundantBlocks(); } private LatticeElement getLatticeElement(Value value) { From e8668c1bb1472739856eaf9d0d0f4cade59afb18 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 13 Jun 2023 17:10:03 +0200 Subject: [PATCH 089/153] Add test for creating overlapping minified ranges Bug: b/286781273 Change-Id: Ic0e9a9dfc1acf69105a4a6f43f15d0998d92ab71 --- .../android/tools/r8/naming/ClassNaming.java | 3 + .../r8/naming/ClassNamingForMapApplier.java | 5 + .../r8/naming/ClassNamingForNameMapper.java | 22 +++++ .../tools/r8/utils/InternalOptions.java | 2 + ...appedPositionToClassNameMapperBuilder.java | 3 + .../tools/r8/naming/SameLineMappingTest.java | 92 +++++++++++++++++++ 6 files changed, 127 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/naming/SameLineMappingTest.java diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java index ed80ba94cc..1357f2e7ce 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java @@ -4,6 +4,7 @@ package com.android.tools.r8.naming; import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange; +import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.naming.MemberNaming.Signature; import com.android.tools.r8.naming.mappinginformation.MappingInformation; import com.android.tools.r8.utils.ThrowingConsumer; @@ -33,6 +34,8 @@ public abstract MappedRange addMappedRange( public abstract void addMappingInformation( MappingInformation info, Consumer onProhibitedAddition); + + public abstract boolean hasNoOverlappingRangesForSignature(MethodSignature residualSignature); } MemberNaming lookup(Signature renamedSignature); diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java index a63f263178..ac54eba6d0 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java @@ -105,6 +105,11 @@ public void addMappingInformation( MappingInformation info, Consumer onProhibitedAddition) { // Intentionally empty. } + + @Override + public boolean hasNoOverlappingRangesForSignature(MethodSignature residualSignature) { + return true; + } } static Builder builder( diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java index ab3c1509c8..cfee5d8319 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java @@ -137,6 +137,28 @@ public void addMappingInformation( originalSourceFileConsumer.accept(originalName, info.asFileNameInformation().getFileName()); } } + + @Override + public boolean hasNoOverlappingRangesForSignature(MethodSignature residualSignature) { + List mappedRanges = mappedRangesByName.get(residualSignature.getName()); + if (mappedRanges == null) { + return true; + } + List nonEmptyMinifiedRanges = + ListUtils.filter(mappedRanges, range -> range.minifiedRange != null); + nonEmptyMinifiedRanges.sort(Comparator.comparing(range -> range.minifiedRange.from)); + Range lastRange = new Range(-1, -1); + for (MappedRange range : nonEmptyMinifiedRanges) { + if (range.minifiedRange.equals(lastRange)) { + continue; + } + if (range.minifiedRange.from <= lastRange.to) { + return false; + } + lastRange = range.minifiedRange; + } + return true; + } } /** List of MappedRanges that belong to the same renamed name. */ 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 538b9ee49c..1b8dfb5b46 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -2092,6 +2092,8 @@ public static class TestingOptions { public boolean calculateItemUseCountInDexDumpSingleUseStrings = false; public boolean enableBinopOptimization = true; + // TODO(b/286781273): Set to true when fixed. + public boolean checkForOverlappingMinifiedRanges = false; private DeterminismChecker getDeterminismChecker() { // Lazily read the env-var so that it can be set after options init. diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java index 5578b1fb91..32eb051f83 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java +++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java @@ -384,6 +384,9 @@ public MappedPositionToClassNamingBuilder addMappedPositions( } i = j; } + assert mappedPositions.size() <= 1 + || !appView.options().getTestingOptions().checkForOverlappingMinifiedRanges + || getBuilder().hasNoOverlappingRangesForSignature(residualSignature); return this; } diff --git a/src/test/java/com/android/tools/r8/naming/SameLineMappingTest.java b/src/test/java/com/android/tools/r8/naming/SameLineMappingTest.java new file mode 100644 index 0000000000..ce0b7df22f --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/SameLineMappingTest.java @@ -0,0 +1,92 @@ +// Copyright (c) 2023, 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.naming; + +import static org.junit.Assert.assertThrows; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.IntBox; +import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** This is a regression test for b/286781273 */ +@RunWith(Parameterized.class) +public class SameLineMappingTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testR8() throws Exception { + IntBox box = new IntBox(0); + MethodReference main = + Reference.methodFromMethod(Main.class.getDeclaredMethod("main", String[].class)); + // TODO(b/286781273): We should not throw an exception here. + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addProgramClassFileData( + transformer(Main.class) + .setPredictiveLineNumbering( + (context, line) -> { + if (context.getReference().equals(main)) { + return (box.getAndIncrement() % 2) + 1; + } + return line; + }) + .transform()) + .setMinApi(parameters) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .addKeepAttributeLineNumberTable() + .addOptionsModification( + options -> { + options.lineNumberOptimization = LineNumberOptimization.OFF; + options.getTestingOptions().checkForOverlappingMinifiedRanges = true; + }) + .compile()); + } + + public static class Main { + + public static void main(String[] args) { + String val1 = foo(); + canThrow(val1); + String val2 = foo(); + canThrow(val2); + } + + @NeverInline + private static String foo() { + if (System.currentTimeMillis() == 0) { + throw new RuntimeException("Hello World"); + } + return "Hello World!"; + } + + @NeverInline + private static void canThrow(String str) { + if (System.currentTimeMillis() == 0) { + throw new RuntimeException(str); + } + System.out.println(str); + } + } +} From 8655e5155ca5a3432d98491c7ddd3589c5dca896 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 13 Jun 2023 17:11:13 +0200 Subject: [PATCH 090/153] Allow for identical ranges to have same delta when minifying ranges Bug: b/286781273 Change-Id: I68630d7d1e253d175fa284d5bee6766f7e653551 --- .../r8/naming/ClassNamingForNameMapper.java | 28 ++++++++---- .../tools/r8/utils/InternalOptions.java | 2 - ...appedPositionToClassNameMapperBuilder.java | 7 ++- .../tools/r8/naming/SameLineMappingTest.java | 45 ++++++++----------- 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java index cfee5d8319..2323d03852 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java @@ -146,16 +146,26 @@ public boolean hasNoOverlappingRangesForSignature(MethodSignature residualSignat } List nonEmptyMinifiedRanges = ListUtils.filter(mappedRanges, range -> range.minifiedRange != null); - nonEmptyMinifiedRanges.sort(Comparator.comparing(range -> range.minifiedRange.from)); - Range lastRange = new Range(-1, -1); - for (MappedRange range : nonEmptyMinifiedRanges) { - if (range.minifiedRange.equals(lastRange)) { - continue; - } - if (range.minifiedRange.from <= lastRange.to) { - return false; + if (nonEmptyMinifiedRanges.isEmpty()) { + return true; + } + MappedRangesOfName mappedRangesOfName = new MappedRangesOfName(nonEmptyMinifiedRanges); + for (MappedRangesOfName partition : mappedRangesOfName.partitionOnMethodSignature()) { + List mappedRangesForSignature = + ListUtils.sort( + partition.getMappedRanges(), + Comparator.comparing(range -> range.minifiedRange.from)); + Range lastRange = new Range(-1, -1); + for (MappedRange range : mappedRangesForSignature) { + if (range.minifiedRange.equals(lastRange)) { + continue; + } + if (range.minifiedRange.from <= lastRange.to) { + assert false; + return false; + } + lastRange = range.minifiedRange; } - lastRange = range.minifiedRange; } return true; } 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 1b8dfb5b46..538b9ee49c 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -2092,8 +2092,6 @@ public static class TestingOptions { public boolean calculateItemUseCountInDexDumpSingleUseStrings = false; public boolean enableBinopOptimization = true; - // TODO(b/286781273): Set to true when fixed. - public boolean checkForOverlappingMinifiedRanges = false; private DeterminismChecker getDeterminismChecker() { // Lazily read the env-var so that it can be set after options init. diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java index 32eb051f83..ffa8c2a658 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java +++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java @@ -385,7 +385,6 @@ public MappedPositionToClassNamingBuilder addMappedPositions( i = j; } assert mappedPositions.size() <= 1 - || !appView.options().getTestingOptions().checkForOverlappingMinifiedRanges || getBuilder().hasNoOverlappingRangesForSignature(residualSignature); return this; } @@ -550,11 +549,11 @@ public MappedPositionRange canAddNextMappingToRange( int lastOriginalLine = lastPosition.getPosition().getLine(); boolean hasSameRightHandSide = lastOriginalLine == currentOriginalLine; if (hasSameRightHandSide) { - if (isSameDelta()) { - return OUT_OF_RANGE; - } boolean hasSameLeftHandSide = lastPosition.getObfuscatedLine() == currentPosition.getObfuscatedLine(); + if (isSameDelta()) { + return hasSameLeftHandSide ? SAME_DELTA : OUT_OF_RANGE; + } return (hasSameLeftHandSide && isSingleLine()) ? SINGLE_LINE : RANGE_TO_SINGLE; } if (isRangeToSingle()) { diff --git a/src/test/java/com/android/tools/r8/naming/SameLineMappingTest.java b/src/test/java/com/android/tools/r8/naming/SameLineMappingTest.java index ce0b7df22f..4883a72ae2 100644 --- a/src/test/java/com/android/tools/r8/naming/SameLineMappingTest.java +++ b/src/test/java/com/android/tools/r8/naming/SameLineMappingTest.java @@ -4,9 +4,6 @@ package com.android.tools.r8.naming; -import static org.junit.Assert.assertThrows; - -import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -37,31 +34,25 @@ public void testR8() throws Exception { IntBox box = new IntBox(0); MethodReference main = Reference.methodFromMethod(Main.class.getDeclaredMethod("main", String[].class)); - // TODO(b/286781273): We should not throw an exception here. - assertThrows( - CompilationFailedException.class, - () -> - testForR8(parameters.getBackend()) - .addProgramClassFileData( - transformer(Main.class) - .setPredictiveLineNumbering( - (context, line) -> { - if (context.getReference().equals(main)) { - return (box.getAndIncrement() % 2) + 1; - } - return line; - }) - .transform()) - .setMinApi(parameters) - .addKeepMainRule(Main.class) - .enableInliningAnnotations() - .addKeepAttributeLineNumberTable() - .addOptionsModification( - options -> { - options.lineNumberOptimization = LineNumberOptimization.OFF; - options.getTestingOptions().checkForOverlappingMinifiedRanges = true; + testForR8(parameters.getBackend()) + .addProgramClassFileData( + transformer(Main.class) + .setPredictiveLineNumbering( + (context, line) -> { + if (context.getReference().equals(main)) { + return (box.getAndIncrement() % 2) + 1; + } + return line; }) - .compile()); + .transform()) + .setMinApi(parameters) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .addKeepAttributeLineNumberTable() + .addOptionsModification( + options -> options.lineNumberOptimization = LineNumberOptimization.OFF) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("Hello World!", "Hello World!"); } public static class Main { From acf972c2e2ba497ae774760ef57ab4c17082e233 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Wed, 14 Jun 2023 08:32:23 +0200 Subject: [PATCH 091/153] Update missing companion message to include the full typename Bug: b/287114771 Change-Id: I5688fbd15e6bd16b604f4dd6028db15e962a633e --- .../com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java index 66dc1be340..30711f1baf 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java @@ -49,7 +49,7 @@ static KotlinMetadataDiagnostic missingCompanionObject( "The companion object " + companionObjectName + " could not be found in class " - + clazz.type.getName()); + + clazz.getTypeName()); } static KotlinMetadataDiagnostic unknownClassifier(String classifier) { From fca77cbc0d2e070aa8a046dade7f0710300eef65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Tue, 13 Jun 2023 10:45:56 +0200 Subject: [PATCH 092/153] Make EnumValueOptimizer a CodeRewriterPass Bug: b/284304606 Change-Id: Iaed455751d5a299c5948a395e9c227a657efea03 --- .../GeneratedMessageLiteBuilderShrinker.java | 2 +- .../tools/r8/ir/conversion/IRConverter.java | 4 +- .../passes/ArrayConstructionSimplifier.java | 10 ++-- .../ir/conversion/passes/BinopRewriter.java | 4 +- .../conversion/passes/CodeRewriterPass.java | 14 +++--- .../CommonSubexpressionElimination.java | 4 +- .../passes/DexConstantOptimizer.java | 6 +-- .../passes/NaturalIntLoopRemover.java | 6 +-- ...ParentConstructorHoistingCodeRewriter.java | 7 ++- .../r8/ir/conversion/passes/SplitBranch.java | 6 +-- .../passes/TrivialGotosCollapser.java | 6 +-- .../ir/optimize/enums/EnumValueOptimizer.java | 50 ++++++++++++------- 12 files changed, 63 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java index c6658c4f9a..a5e1ecbc81 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java @@ -332,7 +332,7 @@ public void inlineCallsToDynamicMethod( // Run the enum optimization to optimize all Enum.ordinal() invocations. This is required to // get rid of the enum switch in dynamicMethod(). if (enumValueOptimizer != null) { - enumValueOptimizer.rewriteConstantEnumMethodCalls(code); + enumValueOptimizer.run(code.context(), code); } } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 7aae7017f4..957a34ffef 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -738,9 +738,7 @@ Timing optimize( if (enumValueOptimizer != null) { assert appView.enableWholeProgramOptimizations(); - timing.begin("Rewrite constant enum methods"); - enumValueOptimizer.rewriteConstantEnumMethodCalls(code); - timing.end(); + enumValueOptimizer.run(context, code, timing); } timing.begin("Rewrite array length"); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java index bc8f8fbed5..bbbb807d24 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java @@ -7,7 +7,6 @@ import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.ArrayTypeElement; @@ -84,20 +83,17 @@ */ public class ArrayConstructionSimplifier extends CodeRewriterPass { - private final DexItemFactory dexItemFactory; - public ArrayConstructionSimplifier(AppView appView) { super(appView); - this.dexItemFactory = appView.dexItemFactory(); } @Override - String getTimingId() { + protected String getTimingId() { return "ArrayConstructionSimplifier"; } @Override - void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(ProgramMethod method, IRCode code) { WorkList worklist = WorkList.newIdentityWorkList(code.blocks); while (worklist.hasNext()) { BasicBlock block = worklist.next(); @@ -106,7 +102,7 @@ void rewriteCode(ProgramMethod method, IRCode code) { } @Override - boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { return appView.options().isGeneratingDex(); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java index fd1c7035ca..7c477906bc 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java @@ -239,12 +239,12 @@ boolean isShift() { } @Override - String getTimingId() { + protected String getTimingId() { return "BinopRewriter"; } @Override - boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { return true; } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java index aea343b540..50a469de81 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java @@ -14,11 +14,11 @@ public abstract class CodeRewriterPass { - final AppView appView; - final DexItemFactory dexItemFactory; - final InternalOptions options; + protected final AppView appView; + protected final DexItemFactory dexItemFactory; + protected final InternalOptions options; - CodeRewriterPass(AppView appView) { + protected CodeRewriterPass(AppView appView) { this.appView = appView; this.dexItemFactory = appView.dexItemFactory(); this.options = appView.options(); @@ -39,9 +39,9 @@ public final void run(ProgramMethod method, IRCode code) { } } - abstract String getTimingId(); + protected abstract String getTimingId(); - abstract void rewriteCode(ProgramMethod method, IRCode code); + protected abstract void rewriteCode(ProgramMethod method, IRCode code); - abstract boolean shouldRewriteCode(ProgramMethod method, IRCode code); + protected abstract boolean shouldRewriteCode(ProgramMethod method, IRCode code); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java index fb168cda3f..036bd0f960 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java @@ -35,12 +35,12 @@ protected String getTimingId() { } @Override - boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { return true; } @Override - void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(ProgramMethod method, IRCode code) { int noCandidate = code.reserveMarkingColor(); if (hasCSECandidate(code, noCandidate)) { final ListMultimap, Value> instructionToValue = diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java index d170c0b25e..7a34b6316f 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java @@ -59,18 +59,18 @@ public DexConstantOptimizer(AppView appView, ConstantCanonicalizer constantCa } @Override - String getTimingId() { + protected String getTimingId() { return "DexConstantOptimizer"; } @Override - void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(ProgramMethod method, IRCode code) { useDedicatedConstantForLitInstruction(code); shortenLiveRanges(code, constantCanonicalizer); } @Override - boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { return true; } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java index 16e9415ba0..298020d105 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java @@ -36,12 +36,12 @@ public NaturalIntLoopRemover(AppView appView) { } @Override - String getTimingId() { + protected String getTimingId() { return "NaturalIntLoopRemover"; } @Override - void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(ProgramMethod method, IRCode code) { boolean loopRemoved = false; for (BasicBlock comparisonBlockCandidate : code.blocks) { if (isComparisonBlock(comparisonBlockCandidate)) { @@ -56,7 +56,7 @@ void rewriteCode(ProgramMethod method, IRCode code) { } @Override - boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { return appView.options().enableLoopUnrolling; } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java index 06e410bc68..685926d632 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java @@ -43,12 +43,12 @@ public ParentConstructorHoistingCodeRewriter(AppView appView) { } @Override - String getTimingId() { + protected String getTimingId() { return "Parent constructor hoisting pass"; } @Override - void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(ProgramMethod method, IRCode code) { for (InvokeDirect invoke : getOrComputeSideEffectFreeConstructorCalls(code)) { hoistSideEffectFreeConstructorCall(code, invoke); } @@ -136,9 +136,8 @@ private boolean hoistSideEffectFreeConstructorCallIntoPredecessorBlock( /** Only run this when the rewriting may actually enable more constructor inlining. */ @Override - boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { if (!appView.hasClassHierarchy()) { - assert !appView.enableWholeProgramOptimizations(); return false; } if (!method.getDefinition().isInstanceInitializer() diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java index 8da51196da..9290a7710b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java @@ -34,12 +34,12 @@ public SplitBranch(AppView appView) { } @Override - String getTimingId() { + protected String getTimingId() { return "SplitBranch"; } @Override - boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { return true; } @@ -54,7 +54,7 @@ boolean shouldRewriteCode(ProgramMethod method, IRCode code) { * known boolean values. */ @Override - void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(ProgramMethod method, IRCode code) { List candidates = computeCandidates(code); if (candidates.isEmpty()) { return; diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java index 7c9b4d0046..fef138e31c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java @@ -29,12 +29,12 @@ public TrivialGotosCollapser(AppView appView) { } @Override - String getTimingId() { + protected String getTimingId() { return "TrivialGotosCollapser"; } @Override - void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(ProgramMethod method, IRCode code) { assert code.isConsistentGraph(appView); List blocksToRemove = new ArrayList<>(); // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove @@ -77,7 +77,7 @@ void rewriteCode(ProgramMethod method, IRCode code) { } @Override - boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { return true; } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java index 9a88b2ec3d..3fb32425e7 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java @@ -14,6 +14,7 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; @@ -33,6 +34,7 @@ import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass; import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.ArrayUtils; @@ -47,21 +49,31 @@ import java.util.Arrays; import java.util.Set; -public class EnumValueOptimizer { - - private final AppView appView; - private final DexItemFactory factory; +public class EnumValueOptimizer extends CodeRewriterPass { public EnumValueOptimizer(AppView appView) { - this.appView = appView; - this.factory = appView.dexItemFactory(); + super(appView); + } + + @Override + protected String getTimingId() { + return "EnumValueOptimizer"; + } + + @Override + protected void rewriteCode(ProgramMethod method, IRCode code) { + rewriteConstantEnumMethodCalls(code); + } + + @Override + protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + return code.metadata().mayHaveInvokeMethodWithReceiver(); } @SuppressWarnings("ConstantConditions") - public void rewriteConstantEnumMethodCalls(IRCode code) { + private void rewriteConstantEnumMethodCalls(IRCode code) { IRMetadata metadata = code.metadata(); - if (!metadata.mayHaveInvokeMethodWithReceiver() - && !(metadata.mayHaveInvokeStatic() && metadata.mayHaveArrayLength())) { + if (!metadata.mayHaveInvokeMethodWithReceiver()) { return; } @@ -76,14 +88,15 @@ public void rewriteConstantEnumMethodCalls(IRCode code) { if (!receiver.getType().isClassType() || !appView .appInfo() - .isSubtype(receiver.getType().asClassType().getClassType(), factory.enumType)) { + .isSubtype( + receiver.getType().asClassType().getClassType(), dexItemFactory.enumType)) { continue; } DexMethod invokedMethod = methodWithReceiver.getInvokedMethod(); - boolean isOrdinalInvoke = invokedMethod.match(factory.enumMembers.ordinalMethod); - boolean isNameInvoke = invokedMethod.match(factory.enumMembers.nameMethod); - boolean isToStringInvoke = invokedMethod.match(factory.enumMembers.toString); + boolean isOrdinalInvoke = invokedMethod.match(dexItemFactory.enumMembers.ordinalMethod); + boolean isNameInvoke = invokedMethod.match(dexItemFactory.enumMembers.nameMethod); + boolean isToStringInvoke = invokedMethod.match(dexItemFactory.enumMembers.toString); if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) { continue; } @@ -161,9 +174,10 @@ public void rewriteConstantEnumMethodCalls(IRCode code) { appView .appInfo() .resolveMethodOnClassLegacy( - enumFieldType.getClassType(), factory.objectMembers.toString) + enumFieldType.getClassType(), dexItemFactory.objectMembers.toString) .getSingleTarget(); - if (singleTarget != null && singleTarget.getReference() != factory.enumMembers.toString) { + if (singleTarget != null + && singleTarget.getReference() != dexItemFactory.enumMembers.toString) { continue; } @@ -334,7 +348,7 @@ private Int2IntArrayMap computeOrdinalToTargetMap( enumInstanceField, appView .appInfo() - .resolveField(factory.enumMembers.ordinalField, code.context()) + .resolveField(dexItemFactory.enumMembers.ordinalField, code.context()) .getResolvedField()); } if (ordinalValue == null) { @@ -350,14 +364,14 @@ private Int2IntArrayMap computeOrdinalToTargetMap( private SingleStringValue getNameValue( IRCode code, AbstractValue abstractValue, boolean neverNull) { AbstractValue ordinalValue = - getEnumFieldValue(code, abstractValue, factory.enumMembers.nameField, neverNull); + getEnumFieldValue(code, abstractValue, dexItemFactory.enumMembers.nameField, neverNull); return ordinalValue == null ? null : ordinalValue.asSingleStringValue(); } private SingleNumberValue getOrdinalValue( IRCode code, AbstractValue abstractValue, boolean neverNull) { AbstractValue ordinalValue = - getEnumFieldValue(code, abstractValue, factory.enumMembers.ordinalField, neverNull); + getEnumFieldValue(code, abstractValue, dexItemFactory.enumMembers.ordinalField, neverNull); return ordinalValue == null ? null : ordinalValue.asSingleNumberValue(); } From 7aac5c99d50f62be2f0f3078be25dafb931c5342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Tue, 13 Jun 2023 12:17:26 +0200 Subject: [PATCH 093/153] TrivialCheckCastAndInstanceOfRemover as CodeRewriterPass Bug: b/284304606 Change-Id: I9f9f7245187b37c2a5c16b18347db242a4b1a9de --- .../GeneratedMessageLiteBuilderShrinker.java | 2 +- .../tools/r8/ir/conversion/CfBuilder.java | 4 +- .../tools/r8/ir/conversion/IRConverter.java | 24 +++++----- .../r8/ir/conversion/IRToDexFinalizer.java | 4 +- .../passes/ArrayConstructionSimplifier.java | 5 +-- .../ir/conversion/passes/BinopRewriter.java | 5 +-- .../conversion/passes/CodeRewriterPass.java | 40 +++++++++++++---- .../CommonSubexpressionElimination.java | 5 +-- .../passes/DexConstantOptimizer.java | 5 +-- .../passes/NaturalIntLoopRemover.java | 5 +-- ...ParentConstructorHoistingCodeRewriter.java | 9 ++-- .../r8/ir/conversion/passes/SplitBranch.java | 5 +-- .../TrivialCheckCastAndInstanceOfRemover.java | 45 ++++++++----------- .../passes/TrivialGotosCollapser.java | 5 +-- .../optimize/classinliner/ClassInliner.java | 3 +- .../ir/optimize/enums/EnumValueOptimizer.java | 11 +++-- .../optimize/TrivialGotoEliminationTest.java | 5 ++- .../ArrayWithDataLengthRewriteTest.java | 3 +- 18 files changed, 95 insertions(+), 90 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java index a5e1ecbc81..20ac50a4b0 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java @@ -332,7 +332,7 @@ public void inlineCallsToDynamicMethod( // Run the enum optimization to optimize all Enum.ordinal() invocations. This is required to // get rid of the enum switch in dynamicMethod(). if (enumValueOptimizer != null) { - enumValueOptimizer.run(code.context(), code); + enumValueOptimizer.run(code, Timing.empty()); } } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java index ae68050f91..e1b5d36eb7 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java @@ -198,7 +198,7 @@ public CfCode build(DeadCodeRemover deadCodeRemover, Timing timing) { timing.begin("BasicBlock peephole optimizations"); if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) { for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) { - trivialGotosCollapser.run(code.context(), code, timing); + trivialGotosCollapser.run(code, timing); PeepholeOptimizer.removeIdenticalPredecessorBlocks(code, registerAllocator); PeepholeOptimizer.shareIdenticalBlockSuffix( code, registerAllocator, SUFFIX_SHARING_OVERHEAD); @@ -208,7 +208,7 @@ public CfCode build(DeadCodeRemover deadCodeRemover, Timing timing) { timing.time("Rewrite Iinc patterns", this::rewriteIincPatterns); - trivialGotosCollapser.run(code.context(), code, timing); + trivialGotosCollapser.run(code, timing); timing.begin("Remove redundant debug positions"); DexBuilder.removeRedundantDebugPositions(code); timing.end(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 957a34ffef..700422b1a2 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -731,28 +731,26 @@ Timing optimize( assert code.verifyTypes(appView); - timing.begin("Remove trivial type checks/casts"); new TrivialCheckCastAndInstanceOfRemover(appView) - .run(code, context, methodProcessor, methodProcessingContext); - timing.end(); + .run(code, methodProcessor, methodProcessingContext, timing); if (enumValueOptimizer != null) { assert appView.enableWholeProgramOptimizations(); - enumValueOptimizer.run(context, code, timing); + enumValueOptimizer.run(code, timing); } timing.begin("Rewrite array length"); codeRewriter.rewriteKnownArrayLengthCalls(code); timing.end(); - new NaturalIntLoopRemover(appView).run(context, code); + new NaturalIntLoopRemover(appView).run(code, timing); if (assertionErrorTwoArgsConstructorRewriter != null) { timing.begin("Rewrite AssertionError"); assertionErrorTwoArgsConstructorRewriter.rewrite( code, methodProcessor, methodProcessingContext); timing.end(); } - commonSubexpressionElimination.run(context, code, timing); - new ArrayConstructionSimplifier(appView).run(context, code, timing); + commonSubexpressionElimination.run(code, timing); + new ArrayConstructionSimplifier(appView).run(code, timing); timing.begin("Rewrite move result"); codeRewriter.rewriteMoveResult(code); timing.end(); @@ -769,13 +767,11 @@ Timing optimize( timing.end(); timing.begin("Simplify control flow"); if (new BranchSimplifier(appView).simplifyBranches(code)) { - timing.begin("Remove trivial type checks/casts"); new TrivialCheckCastAndInstanceOfRemover(appView) - .run(code, context, methodProcessor, methodProcessingContext); - timing.end(); + .run(code, methodProcessor, methodProcessingContext, timing); } timing.end(); - splitBranch.run(code.context(), code, timing); + splitBranch.run(code, timing); if (options.enableRedundantConstNumberOptimization) { timing.begin("Remove const numbers"); codeRewriter.redundantConstNumberRemoval(code); @@ -787,7 +783,7 @@ Timing optimize( timing.end(); } if (binopRewriter != null) { - binopRewriter.run(context, code, timing); + binopRewriter.run(code, timing); } if (options.testing.invertConditionals) { @@ -878,7 +874,7 @@ Timing optimize( constantCanonicalizer.canonicalize(); timing.end(); previous = printMethod(code, "IR after constant canonicalization (SSA)", previous); - new DexConstantOptimizer(appView, constantCanonicalizer).run(context, code, timing); + new DexConstantOptimizer(appView, constantCanonicalizer).run(code, timing); previous = printMethod(code, "IR after dex constant optimization (SSA)", previous); } @@ -905,7 +901,7 @@ Timing optimize( deadCodeRemover.run(code, timing); - new ParentConstructorHoistingCodeRewriter(appView).run(context, code, timing); + new ParentConstructorHoistingCodeRewriter(appView).run(code, timing); BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder = BytecodeMetadataProvider.builder(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java index ddcd9aca2e..dbd35f7911 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java @@ -64,14 +64,14 @@ private RegisterAllocator performRegisterAllocation( if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) { timing.begin("Peephole optimize"); for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) { - trivialGotosCollapser.run(code.context(), code, timing); + trivialGotosCollapser.run(code, timing); PeepholeOptimizer.optimize(appView, code, registerAllocator); } timing.end(); } timing.begin("Clean up"); CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator); - trivialGotosCollapser.run(code.context(), code, timing); + trivialGotosCollapser.run(code, timing); timing.end(); return registerAllocator; } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java index bbbb807d24..63e6b3690e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java @@ -8,7 +8,6 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.ArrayTypeElement; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.ArrayPut; @@ -93,7 +92,7 @@ protected String getTimingId() { } @Override - protected void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(IRCode code) { WorkList worklist = WorkList.newIdentityWorkList(code.blocks); while (worklist.hasNext()) { BasicBlock block = worklist.next(); @@ -102,7 +101,7 @@ protected void rewriteCode(ProgramMethod method, IRCode code) { } @Override - protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(IRCode code) { return appView.options().isGeneratingDex(); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java index 7c477906bc..24fad2eea9 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java @@ -7,7 +7,6 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Add; import com.android.tools.r8.ir.code.And; @@ -244,12 +243,12 @@ protected String getTimingId() { } @Override - protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(IRCode code) { return true; } @Override - public void rewriteCode(ProgramMethod method, IRCode code) { + public void rewriteCode(IRCode code) { InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) { Instruction next = iterator.next(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java index 50a469de81..94e30d3df7 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java @@ -4,11 +4,13 @@ package com.android.tools.r8.ir.conversion.passes; +import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexItemFactory; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Timing; @@ -25,23 +27,43 @@ protected CodeRewriterPass(AppView appView) { } @SuppressWarnings("unchecked") - AppView appView() { + protected AppView appView() { return (AppView) appView; } - public final void run(ProgramMethod method, IRCode code, Timing timing) { - timing.time(getTimingId(), () -> run(method, code)); + public final void run( + IRCode code, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext, + Timing timing) { + timing.time(getTimingId(), () -> run(code, methodProcessor, methodProcessingContext)); } - public final void run(ProgramMethod method, IRCode code) { - if (shouldRewriteCode(method, code)) { - rewriteCode(method, code); + public final void run(IRCode code, Timing timing) { + timing.time(getTimingId(), () -> run(code, null, null)); + } + + private void run( + IRCode code, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext) { + if (shouldRewriteCode(code)) { + rewriteCode(code, methodProcessor, methodProcessingContext); } } protected abstract String getTimingId(); - protected abstract void rewriteCode(ProgramMethod method, IRCode code); + protected void rewriteCode(IRCode code) { + throw new Unreachable("Should Override or use overload"); + } + + protected void rewriteCode( + IRCode code, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext) { + rewriteCode(code); + } - protected abstract boolean shouldRewriteCode(ProgramMethod method, IRCode code); + protected abstract boolean shouldRewriteCode(IRCode code); } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java index 036bd0f960..5d88dedede 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java @@ -6,7 +6,6 @@ import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.Binop; import com.android.tools.r8.ir.code.CatchHandlers; @@ -35,12 +34,12 @@ protected String getTimingId() { } @Override - protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(IRCode code) { return true; } @Override - protected void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(IRCode code) { int noCandidate = code.reserveMarkingColor(); if (hasCSECandidate(code, noCandidate)) { final ListMultimap, Value> instructionToValue = diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java index 7a34b6316f..c6e07ed844 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java @@ -14,7 +14,6 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.Binop; @@ -64,13 +63,13 @@ protected String getTimingId() { } @Override - protected void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(IRCode code) { useDedicatedConstantForLitInstruction(code); shortenLiveRanges(code, constantCanonicalizer); } @Override - protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(IRCode code) { return true; } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java index 298020d105..be7840ffc0 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java @@ -7,7 +7,6 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; @@ -41,7 +40,7 @@ protected String getTimingId() { } @Override - protected void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(IRCode code) { boolean loopRemoved = false; for (BasicBlock comparisonBlockCandidate : code.blocks) { if (isComparisonBlock(comparisonBlockCandidate)) { @@ -56,7 +55,7 @@ protected void rewriteCode(ProgramMethod method, IRCode code) { } @Override - protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(IRCode code) { return appView.options().enableLoopUnrolling; } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java index 685926d632..4f96ce7335 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java @@ -48,7 +48,7 @@ protected String getTimingId() { } @Override - protected void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(IRCode code) { for (InvokeDirect invoke : getOrComputeSideEffectFreeConstructorCalls(code)) { hoistSideEffectFreeConstructorCall(code, invoke); } @@ -136,15 +136,16 @@ private boolean hoistSideEffectFreeConstructorCallIntoPredecessorBlock( /** Only run this when the rewriting may actually enable more constructor inlining. */ @Override - protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(IRCode code) { if (!appView.hasClassHierarchy()) { return false; } - if (!method.getDefinition().isInstanceInitializer() + ProgramMethod context = code.context(); + if (!context.getDefinition().isInstanceInitializer() || !options.canInitNewInstanceUsingSuperclassConstructor()) { return false; } - KeepMethodInfo keepInfo = appView.getKeepInfo(method); + KeepMethodInfo keepInfo = appView.getKeepInfo(context); return keepInfo.isOptimizationAllowed(options) && keepInfo.isShrinkingAllowed(options) && hoistingMayRemoveInstancePutToUninitializedThis(code); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java index 9290a7710b..fbb3afd48c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java @@ -6,7 +6,6 @@ import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.ConstNumber; @@ -39,7 +38,7 @@ protected String getTimingId() { } @Override - protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(IRCode code) { return true; } @@ -54,7 +53,7 @@ protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { * known boolean values. */ @Override - protected void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(IRCode code) { List candidates = computeCandidates(code); if (candidates.isEmpty()) { return; diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java index bd09aaef54..cbce46f391 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java @@ -8,9 +8,9 @@ import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.graph.AccessControl; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; @@ -22,7 +22,6 @@ import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.InstanceOf; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; @@ -33,43 +32,35 @@ import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.Sets; import java.util.Set; -public class TrivialCheckCastAndInstanceOfRemover { - - private final AppView appView; - private final InternalOptions options; - private final DexItemFactory dexItemFactory; +public class TrivialCheckCastAndInstanceOfRemover extends CodeRewriterPass { public TrivialCheckCastAndInstanceOfRemover(AppView appView) { - this.appView = appView; - this.options = appView.options(); - this.dexItemFactory = appView.dexItemFactory(); + super(appView); + } + + @Override + protected String getTimingId() { + return "TrivialCheckCastAndInstanceOfRemover"; } - public void run( + @Override + protected boolean shouldRewriteCode(IRCode code) { + return appView.enableWholeProgramOptimizations() + && appView.options().testing.enableCheckCastAndInstanceOfRemoval + && (code.metadata().mayHaveCheckCast() || code.metadata().mayHaveInstanceOf()); + } + + @Override + protected void rewriteCode( IRCode code, - ProgramMethod context, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { - if (!appView.enableWholeProgramOptimizations()) { - return; - } - assert appView.appInfo().hasLiveness(); AppView appViewWithLiveness = appView.withLiveness(); - if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) { - return; - } - - IRMetadata metadata = code.metadata(); - if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) { - return; - } - // If we can remove a CheckCast it is due to us having at least as much information about the // type as the CheckCast gives. We then need to propagate that information to the users of // the CheckCast to ensure further optimizations and removals of CheckCast: @@ -101,7 +92,7 @@ public void run( current.asCheckCast(), it, code, - context, + code.context(), affectedValues, methodProcessor, methodProcessingContext); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java index fef138e31c..4f8ee2e80f 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java @@ -6,7 +6,6 @@ import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; @@ -34,7 +33,7 @@ protected String getTimingId() { } @Override - protected void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(IRCode code) { assert code.isConsistentGraph(appView); List blocksToRemove = new ArrayList<>(); // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove @@ -77,7 +76,7 @@ protected void rewriteCode(ProgramMethod method, IRCode code) { } @Override - protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(IRCode code) { return true; } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index 05a01c0384..d3d2a08bff 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -31,6 +31,7 @@ import com.android.tools.r8.ir.optimize.string.StringOptimizer; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.LazyBox; +import com.android.tools.r8.utils.Timing; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.util.Iterator; @@ -249,7 +250,7 @@ public final void processMethodCode( // have more information about the types of the arguments at the call site. This is // particularly important for bridge methods. new TrivialCheckCastAndInstanceOfRemover(appView) - .run(code, method, methodProcessor, methodProcessingContext); + .run(code, methodProcessor, methodProcessingContext, Timing.empty()); // If a method was inlined we may be able to prune additional branches. new BranchSimplifier(appView).simplifyBranches(code); // If a method was inlined we may see more trivial computation/conversion of String. diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java index 3fb32425e7..c94b03ed22 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java @@ -14,7 +14,6 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; @@ -61,12 +60,12 @@ protected String getTimingId() { } @Override - protected void rewriteCode(ProgramMethod method, IRCode code) { + protected void rewriteCode(IRCode code) { rewriteConstantEnumMethodCalls(code); } @Override - protected boolean shouldRewriteCode(ProgramMethod method, IRCode code) { + protected boolean shouldRewriteCode(IRCode code) { return code.metadata().mayHaveInvokeMethodWithReceiver(); } @@ -86,7 +85,7 @@ private void rewriteConstantEnumMethodCalls(IRCode code) { InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver(); Value receiver = methodWithReceiver.getReceiver().getAliasedValue(); if (!receiver.getType().isClassType() - || !appView + || !appView() .appInfo() .isSubtype( receiver.getType().asClassType().getClassType(), dexItemFactory.enumType)) { @@ -171,7 +170,7 @@ private void rewriteConstantEnumMethodCalls(IRCode code) { } DexEncodedMethod singleTarget = - appView + appView() .appInfo() .resolveMethodOnClassLegacy( enumFieldType.getClassType(), dexItemFactory.objectMembers.toString) @@ -455,7 +454,7 @@ private EnumSwitchInfo analyzeSwitchOverEnum(IntSwitch switchInsn) { return null; } StaticGet staticGet = array.asStaticGet(); - Int2ReferenceMap indexMap = appView.appInfo().getSwitchMap(staticGet.getField()); + Int2ReferenceMap indexMap = appView().appInfo().getSwitchMap(staticGet.getField()); if (indexMap == null || indexMap.isEmpty()) { return null; } diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java index 8a8ab5172b..9e02653ab6 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java @@ -33,6 +33,7 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Timing; import com.google.common.collect.ImmutableList; import java.util.LinkedList; import org.junit.Test; @@ -108,7 +109,7 @@ public void trivialGotoInEntryBlock() throws Exception { IRMetadata.unknown(), Origin.unknown(), new MutableMethodConversionOptions(options)); - new TrivialGotosCollapser(appView).run(code.context(), code); + new TrivialGotosCollapser(appView).run(code, Timing.empty()); assertTrue(code.entryBlock().isTrivialGoto()); assertTrue(blocks.contains(block0)); assertTrue(blocks.contains(block1)); @@ -197,7 +198,7 @@ public void trivialGotoLoopAsFallthrough() throws Exception { IRMetadata.unknown(), Origin.unknown(), new MutableMethodConversionOptions(options)); - new TrivialGotosCollapser(appView).run(code.context(), code); + new TrivialGotosCollapser(appView).run(code, Timing.empty()); assertTrue(block0.getInstructions().get(1).isIf()); assertEquals(block1, block0.getInstructions().get(1).asIf().fallthroughBlock()); assertTrue(blocks.containsAll(ImmutableList.of(block0, block1, block2, block3))); diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java index a09de37aa2..7f9c7bddcf 100644 --- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java +++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java @@ -14,6 +14,7 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.conversion.passes.ArrayConstructionSimplifier; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; @@ -64,7 +65,7 @@ public void r8() throws Exception { } private void transformArray(IRCode irCode, AppView appView) { - new ArrayConstructionSimplifier(appView).run(irCode.context(), irCode); + new ArrayConstructionSimplifier(appView).run(irCode, Timing.empty()); String name = irCode.context().getReference().getName().toString(); if (name.contains("filledArrayData")) { assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData)); From 956f3b37577bfc9d819b72c0e09a5e8e5f0dd5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Tue, 13 Jun 2023 12:17:55 +0200 Subject: [PATCH 094/153] Split KnownArrayLengthRewriter Bug: b/284304606 Change-Id: I8adf12e3f1cd287ab8ea7ca12265f07134c9c835 --- .../tools/r8/ir/conversion/IRConverter.java | 5 +- .../passes/KnownArrayLengthRewriter.java | 81 +++++++++++++++++++ .../tools/r8/ir/optimize/CodeRewriter.java | 51 ------------ 3 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 700422b1a2..2a2813f685 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -32,6 +32,7 @@ import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer; +import com.android.tools.r8.ir.conversion.passes.KnownArrayLengthRewriter; import com.android.tools.r8.ir.conversion.passes.NaturalIntLoopRemover; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; @@ -739,9 +740,7 @@ Timing optimize( enumValueOptimizer.run(code, timing); } - timing.begin("Rewrite array length"); - codeRewriter.rewriteKnownArrayLengthCalls(code); - timing.end(); + new KnownArrayLengthRewriter(appView).run(code, timing); new NaturalIntLoopRemover(appView).run(code, timing); if (assertionErrorTwoArgsConstructorRewriter != null) { timing.begin("Rewrite AssertionError"); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java new file mode 100644 index 0000000000..086a7e00a5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java @@ -0,0 +1,81 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.ir.code.ArrayLength; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.Phi; +import com.android.tools.r8.ir.code.Value; +import java.util.Set; + +public class KnownArrayLengthRewriter extends CodeRewriterPass { + + public KnownArrayLengthRewriter(AppView appView) { + super(appView); + } + + @Override + protected String getTimingId() { + return "KnownArrayLengthRewriter"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return code.metadata().mayHaveArrayLength(); + } + + @Override + protected void rewriteCode(IRCode code) { + InstructionListIterator iterator = code.instructionListIterator(); + while (iterator.hasNext()) { + Instruction current = iterator.next(); + if (!current.isArrayLength()) { + continue; + } + + ArrayLength arrayLength = current.asArrayLength(); + if (arrayLength.hasOutValue() && arrayLength.outValue().hasLocalInfo()) { + continue; + } + + Value array = arrayLength.array().getAliasedValue(); + if (array.isPhi() || !arrayLength.array().isNeverNull() || array.hasLocalInfo()) { + continue; + } + + AbstractValue abstractValue = array.getAbstractValue(appView, code.context()); + if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) { + continue; + } + Instruction arrayDefinition = array.getDefinition(); + assert arrayDefinition != null; + + Set phiUsers = arrayLength.outValue().uniquePhiUsers(); + if (arrayDefinition.isNewArrayEmpty()) { + Value size = arrayDefinition.asNewArrayEmpty().size(); + arrayLength.outValue().replaceUsers(size); + iterator.removeOrReplaceByDebugLocalRead(); + } else if (arrayDefinition.isNewArrayFilledData()) { + long size = arrayDefinition.asNewArrayFilledData().size; + if (size > Integer.MAX_VALUE) { + continue; + } + iterator.replaceCurrentInstructionWithConstInt(code, (int) size); + } else if (abstractValue.hasKnownArrayLength()) { + iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength()); + } else { + continue; + } + + phiUsers.forEach(Phi::removeTrivialPhi); + } + assert code.isConsistentSSA(appView); + } +} 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 0476582092..4984711307 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 @@ -21,8 +21,6 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; -import com.android.tools.r8.ir.analysis.value.AbstractValue; -import com.android.tools.r8.ir.code.ArrayLength; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.ConstNumber; @@ -40,7 +38,6 @@ import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Move; -import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.SyntheticPosition; import com.android.tools.r8.ir.code.StaticGet; @@ -259,54 +256,6 @@ public static void removeOrReplaceByDebugLocalWrite( } } - public void rewriteKnownArrayLengthCalls(IRCode code) { - InstructionListIterator iterator = code.instructionListIterator(); - while (iterator.hasNext()) { - Instruction current = iterator.next(); - if (!current.isArrayLength()) { - continue; - } - - ArrayLength arrayLength = current.asArrayLength(); - if (arrayLength.hasOutValue() && arrayLength.outValue().hasLocalInfo()) { - continue; - } - - Value array = arrayLength.array().getAliasedValue(); - if (array.isPhi() || !arrayLength.array().isNeverNull() || array.hasLocalInfo()) { - continue; - } - - AbstractValue abstractValue = array.getAbstractValue(appView, code.context()); - if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) { - continue; - } - Instruction arrayDefinition = array.getDefinition(); - assert arrayDefinition != null; - - Set phiUsers = arrayLength.outValue().uniquePhiUsers(); - if (arrayDefinition.isNewArrayEmpty()) { - Value size = arrayDefinition.asNewArrayEmpty().size(); - arrayLength.outValue().replaceUsers(size); - iterator.removeOrReplaceByDebugLocalRead(); - } else if (arrayDefinition.isNewArrayFilledData()) { - long size = arrayDefinition.asNewArrayFilledData().size; - if (size > Integer.MAX_VALUE) { - continue; - } - iterator.replaceCurrentInstructionWithConstInt(code, (int) size); - } else if (abstractValue.hasKnownArrayLength()) { - iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength()); - } else { - continue; - } - - phiUsers.forEach(Phi::removeTrivialPhi); - } - code.removeRedundantBlocks(); - assert code.isConsistentSSA(appView); - } - // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block. private static boolean hasLocalOrLineChangeBetween( Instruction from, Instruction to, DexString localVar) { From 47e4d71c282e075950eae532d9147b51fc741837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gjesse?= Date: Wed, 14 Jun 2023 09:50:41 +0200 Subject: [PATCH 095/153] Remove redundant blocks in enum unboxer Bug: b/281975144 Change-Id: I192580be4c42cb4913f5589ead4942462a919203 --- .../android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java index 05648c326c..fa88164fd7 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java @@ -431,6 +431,7 @@ Set rewriteCode( } } } + code.removeRedundantBlocks(); assert code.isConsistentSSABeforeTypesAreCorrect(appView); return affectedPhis; } From 31c34fb40a51c82f758a507d8e79625134e8001e Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Wed, 14 Jun 2023 10:58:50 +0200 Subject: [PATCH 096/153] Add a check column to check for missing cherry-picks This is an update of commit 11213a84770a1b54320033d101be951bfd5c126a to run the check on the main branch instead of release. Fixed: b/286177952 Change-Id: I2a96b62503dc2fc9d36d12518e2e012e9a2dc385 --- .../global/generated/cr-buildbucket.cfg | 29 +++++++++++++++++++ infra/config/global/generated/luci-milo.cfg | 5 ++++ infra/config/global/generated/luci-notify.cfg | 12 ++++++++ .../global/generated/luci-scheduler.cfg | 16 ++++++++++ infra/config/global/generated/project.cfg | 2 +- infra/config/global/main.star | 18 ++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg index aa285e07e0..661516d9df 100644 --- a/infra/config/global/generated/cr-buildbucket.cfg +++ b/infra/config/global/generated/cr-buildbucket.cfg @@ -76,6 +76,35 @@ buckets { value: 100 } } + builders { + name: "check" + swarming_host: "chrome-swarming.appspot.com" + swarming_tags: "vpython:native-python-wrapper" + dimensions: "cores:8" + dimensions: "cpu:x86-64" + dimensions: "os:Ubuntu-20.04" + dimensions: "pool:luci.r8.ci" + exe { + cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" + cipd_version: "refs/heads/master" + cmd: "luciexe" + } + properties: + '{' + ' "builder_group": "internal.client.r8",' + ' "recipe": "rex",' + ' "test_wrapper": "tools/check-cherry-picks.py"' + '}' + priority: 25 + execution_timeout_secs: 1800 + expiration_secs: 126000 + build_numbers: YES + service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + } builders { name: "desugared_library-head" swarming_host: "chrome-swarming.appspot.com" diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg index b1481fe595..8914541570 100644 --- a/infra/config/global/generated/luci-milo.cfg +++ b/infra/config/global/generated/luci-milo.cfg @@ -15,6 +15,11 @@ consoles { category: "archive" short_name: "archive" } + builders { + name: "buildbucket/luci.r8.ci/check" + category: "archive" + short_name: "check" + } builders { name: "buildbucket/luci.r8.ci/linux-dex_default" category: "R8" diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg index fa8d5b0aab..521dee428a 100644 --- a/infra/config/global/generated/luci-notify.cfg +++ b/infra/config/global/generated/luci-notify.cfg @@ -28,6 +28,18 @@ notifiers { repository: "https://r8.googlesource.com/r8" } } +notifiers { + notifications { + on_failure: true + on_new_failure: true + notify_blamelist {} + } + builders { + bucket: "ci" + name: "check" + repository: "https://r8.googlesource.com/r8" + } +} notifiers { notifications { on_failure: true diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg index e59d20c1e8..f990fdb89a 100644 --- a/infra/config/global/generated/luci-scheduler.cfg +++ b/infra/config/global/generated/luci-scheduler.cfg @@ -34,6 +34,21 @@ job { builder: "archive_release" } } +job { + id: "check" + realm: "ci" + acl_sets: "ci" + triggering_policy { + kind: GREEDY_BATCHING + max_concurrent_invocations: 1 + max_batch_size: 1 + } + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "ci" + builder: "check" + } +} job { id: "desugared_library-head" realm: "ci" @@ -797,6 +812,7 @@ trigger { realm: "ci" acl_sets: "ci" triggers: "archive" + triggers: "check" triggers: "desugared_library-head" triggers: "desugared_library-jdk11_head" triggers: "linux-android-10.0.0" diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg index cfee79c7e6..ee2ef9d548 100644 --- a/infra/config/global/generated/project.cfg +++ b/infra/config/global/generated/project.cfg @@ -7,7 +7,7 @@ name: "r8" access: "group:all" lucicfg { - version: "1.39.4" + version: "1.39.8" package_dir: ".." config_dir: "generated" entry_point: "main.star" diff --git a/infra/config/global/main.star b/infra/config/global/main.star index d9201a8de5..c2ce866146 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star @@ -283,6 +283,24 @@ def archivers(): ) archivers() +r8_builder( + "check", + category = "archive", + dimensions = get_dimensions(), + triggering_policy = scheduler.policy( + kind = scheduler.GREEDY_BATCHING_KIND, + max_batch_size = 1, + max_concurrent_invocations = 1 + ), + priority = 25, + properties = { + "test_wrapper" : "tools/check-cherry-picks.py", + "builder_group" : "internal.client.r8" + }, + execution_timeout = time.minute * 30, + expiration_timeout = time.hour * 35, +) + r8_tester_with_default("linux-dex_default", ["--runtimes=dex-default"]) r8_tester_with_default("linux-none", ["--runtimes=none"]) r8_tester_with_default("linux-jdk8", ["--runtimes=jdk8"]) From b2810a156860d498e046a11f706e8404eb6c5a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Wed, 14 Jun 2023 11:14:04 +0200 Subject: [PATCH 097/153] Add RemoveRedundantBlocks Fix red bots Change-Id: I4d280a35766328e702e13be53d757934fe2f1ca4 --- .../tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java index 086a7e00a5..9ae7aabf0b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java @@ -76,6 +76,7 @@ protected void rewriteCode(IRCode code) { phiUsers.forEach(Phi::removeTrivialPhi); } + code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } } From fc645a240777f7a32bfb1a3aa2206391d1ff1190 Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Wed, 14 Jun 2023 12:37:18 +0200 Subject: [PATCH 098/153] Rename art_cache_dir to command_cache_dir, add environment variable Bug: 286019067 Change-Id: Ibc26ea7dc8a0751d2448e5d97e2bc001169937b4 --- build.gradle | 4 ++-- src/test/java/com/android/tools/r8/ToolHelper.java | 5 +++-- tools/test.py | 13 +++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 3fef251aa2..a4752c4451 100644 --- a/build.gradle +++ b/build.gradle @@ -2254,8 +2254,8 @@ test { task -> if (project.hasProperty('test_dir')) { systemProperty 'test_dir', project.property('test_dir') } - if (project.hasProperty('art_cache_dir')) { - systemProperty 'art_cache_dir', project.property('art_cache_dir') + if (project.hasProperty('command_cache_dir')) { + systemProperty 'command_cache_dir', project.property('command_cache_dir') } if (project.hasProperty('r8lib')) { dependsOn configureTestForR8Lib diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java index 46bdb5dc69..2d41bdba13 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java @@ -657,9 +657,10 @@ public String getHash() { private static class CommandResultCache { private static CommandResultCache INSTANCE = - System.getProperty("art_cache_dir") != null - ? new CommandResultCache(Paths.get(System.getProperty("art_cache_dir"))) + System.getProperty("command_cache_dir") != null + ? new CommandResultCache(Paths.get(System.getProperty("command_cache_dir"))) : null; + private final Path path; public CommandResultCache(Path path) { diff --git a/tools/test.py b/tools/test.py index 6060e041f0..f4adbe1055 100755 --- a/tools/test.py +++ b/tools/test.py @@ -113,8 +113,9 @@ def ParseOptions(): help='Use a custom directory for the test artifacts instead of a' ' temporary (which is automatically removed after the test).' ' Note that the directory will not be cleared before the test.') - result.add_option('--art-cache-dir', '--art_cache_dir', - help='Cache art invocations to this directory, speeds up test runs') + result.add_option('--command-cache-dir', '--command_cache_dir', + help='Cache command invocations to this directory, speeds up test runs', + default=os.environ.get('COMMAND_CACHE_DIR')) result.add_option('--java-home', '--java_home', help='Use a custom java version to run tests.') result.add_option('--java-max-memory-size', '--java_max_memory_size', @@ -318,10 +319,10 @@ def Main(): gradle_args.append('-Ptest_dir=' + options.test_dir) if not os.path.exists(options.test_dir): os.makedirs(options.test_dir) - if options.art_cache_dir: - gradle_args.append('-Part_cache_dir=' + options.art_cache_dir) - if not os.path.exists(options.art_cache_dir): - os.makedirs(options.art_cache_dir) + if options.command_cache_dir: + gradle_args.append('-Pcommand_cache_dir=' + options.command_cache_dir) + if not os.path.exists(options.command_cache_dir): + os.makedirs(options.command_cache_dir) if options.java_home: gradle_args.append('-Dorg.gradle.java.home=' + options.java_home) if options.java_max_memory_size: From 8925a2dfb1bfe26ee6ae42084755bd204d7aff99 Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Wed, 14 Jun 2023 12:43:59 +0200 Subject: [PATCH 099/153] Prefix environment variable with R8 Bug: 286019067 Change-Id: Ic9cd91e0494a1a45de5145266748ad9fcb147b13 --- tools/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/test.py b/tools/test.py index f4adbe1055..e67bea5fdb 100755 --- a/tools/test.py +++ b/tools/test.py @@ -115,7 +115,7 @@ def ParseOptions(): ' Note that the directory will not be cleared before the test.') result.add_option('--command-cache-dir', '--command_cache_dir', help='Cache command invocations to this directory, speeds up test runs', - default=os.environ.get('COMMAND_CACHE_DIR')) + default=os.environ.get('R8_COMMAND_CACHE_DIR')) result.add_option('--java-home', '--java_home', help='Use a custom java version to run tests.') result.add_option('--java-max-memory-size', '--java_max_memory_size', From d1a118589a7b92a76d94c0d41a051f91da570a24 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Wed, 14 Jun 2023 14:48:44 +0200 Subject: [PATCH 100/153] Update new gradle setup to compile r8lib with and without dependencies Bug: b/270105162 Change-Id: I1fd6f44e956805ea69094eb22c93effaf622cb18 --- commonBuildSrc/settings.gradle.kts | 8 +- .../src/main/kotlin/Dependencies.kt | 65 ------- .../src/main/kotlin/DependenciesPlugin.kt | 163 ++++++++++++++++++ d8_r8/gradle.properties | 2 +- d8_r8/keepanno/gradle.properties | 2 +- d8_r8/keepanno/settings.gradle.kts | 18 +- d8_r8/main/build.gradle.kts | 101 ++++++++++- d8_r8/main/gradle.properties | 2 +- d8_r8/main/settings.gradle.kts | 21 ++- d8_r8/r8lib/build.gradle.kts | 97 +++++++++++ d8_r8/r8lib/gradle.properties | 17 ++ d8_r8/r8lib/settings.gradle.kts | 31 ++++ d8_r8/settings.gradle.kts | 35 ++-- d8_r8/test/build.gradle.kts | 91 ++++++---- d8_r8/test/gradle.properties | 2 +- d8_r8/test/settings.gradle.kts | 19 +- .../tests_java_8/build.gradle.kts | 97 ++++++----- .../tests_java_8/gradle.properties | 2 +- .../tests_java_8/settings.gradle.kts | 29 ++-- tools/create_r8lib.py | 7 +- 20 files changed, 586 insertions(+), 223 deletions(-) delete mode 100644 commonBuildSrc/src/main/kotlin/Dependencies.kt create mode 100644 commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt create mode 100644 d8_r8/r8lib/build.gradle.kts create mode 100644 d8_r8/r8lib/gradle.properties create mode 100644 d8_r8/r8lib/settings.gradle.kts diff --git a/commonBuildSrc/settings.gradle.kts b/commonBuildSrc/settings.gradle.kts index f346f8551c..d26745ce5d 100644 --- a/commonBuildSrc/settings.gradle.kts +++ b/commonBuildSrc/settings.gradle.kts @@ -5,10 +5,10 @@ pluginManagement { repositories { maven { - url = uri("file:../third_party/dependencies_new") + url = uri("file:../third_party/dependencies") } maven { - url = uri("file:../third_party/dependencies") + url = uri("file:../third_party/dependencies_new") } } } @@ -16,10 +16,10 @@ pluginManagement { dependencyResolutionManagement { repositories { maven { - url = uri("file:../third_party/dependencies_new") + url = uri("file:../third_party/dependencies") } maven { - url = uri("file:../third_party/dependencies") + url = uri("file:../third_party/dependencies_new") } } } diff --git a/commonBuildSrc/src/main/kotlin/Dependencies.kt b/commonBuildSrc/src/main/kotlin/Dependencies.kt deleted file mode 100644 index 66e88d1d3b..0000000000 --- a/commonBuildSrc/src/main/kotlin/Dependencies.kt +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2023, 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. - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.JavaVersion -import java.io.File - -class DependenciesPlugin: Plugin { - override fun apply(target: Project) { - // Intentionally empty - } -} - -fun Project.getRoot() : File { - var parent = this.projectDir - while (!parent.getName().equals("d8_r8")) { - parent = parent.getParentFile() - } - return parent.getParentFile() -} - -fun File.resolveAll(vararg xs: String) : File { - var that = this; - for (x in xs) { - that = that.resolve(x) - } - return that -} - -object JvmCompatibility { - val sourceCompatibility = JavaVersion.VERSION_11 - val targetCompatibility = JavaVersion.VERSION_11 -} - -object Versions { - const val asmVersion = "9.5" - const val fastUtilVersion = "7.2.0" - const val gsonVersion = "2.7" - const val guavaVersion = "31.1-jre" - const val joptSimpleVersion = "4.6" - const val junitVersion = "4.13-beta-2" - const val kotlinVersion = "1.8.0" - const val kotlinMetadataVersion = "0.6.0" - const val smaliVersion = "3.0.3" - const val errorproneVersion = "2.18.0" -} - -object Deps { - val asm by lazy { "org.ow2.asm:asm:${Versions.asmVersion}" } - val asmUtil by lazy { "org.ow2.asm:asm-util:${Versions.asmVersion}" } - val asmCommons by lazy { "org.ow2.asm:asm-commons:${Versions.asmVersion}" } - val fastUtil by lazy { "it.unimi.dsi:fastutil:${Versions.fastUtilVersion}"} - val gson by lazy { "com.google.code.gson:gson:${Versions.gsonVersion}"} - val guava by lazy { "com.google.guava:guava:${Versions.guavaVersion}" } - val joptSimple by lazy { "net.sf.jopt-simple:jopt-simple:${Versions.joptSimpleVersion}" } - val junit by lazy { "junit:junit:${Versions.junitVersion}"} - val kotlinMetadata by lazy { - "org.jetbrains.kotlinx:kotlinx-metadata-jvm:${Versions.kotlinMetadataVersion}" } - val kotlinStdLib by lazy { "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinVersion}" } - val kotlinReflect by lazy { "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinVersion}" } - val smali by lazy { "com.android.tools.smali:smali:${Versions.smaliVersion}" } - val errorprone by lazy { "com.google.errorprone:error_prone_core:${Versions.errorproneVersion}" } -} diff --git a/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt new file mode 100644 index 0000000000..19b48eeac7 --- /dev/null +++ b/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt @@ -0,0 +1,163 @@ +// Copyright (c) 2023, 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. + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.JavaVersion +import org.gradle.api.Task +import org.gradle.api.tasks.Exec +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths +class DependenciesPlugin: Plugin { + + override fun apply(target: Project) { + // Intentionally empty + } +} + +enum class Jdk(val folder : String) { + JDK_11("jdk-11"), + JDK_17("jdk-17"), + JDK_20("jdk-20"); +} + +fun Project.getRoot() : File { + var parent = this.projectDir + while (!parent.getName().equals("d8_r8")) { + parent = parent.getParentFile() + } + return parent.getParentFile() +} + +fun Project.header(title : String) : String { + return "****** ${title} ******" +} + +/** + * When using composite builds, referecing tasks in other projects do not give a Task but a + * TaskReference. To get outputs from other tasks we need to have a proper task and gradle do not + * provide a way of getting a Task from a TaskReference. We use a trick where create a synthetic + * task that depends on the task of interest, allowing us to look at the graph and obtain the + * actual reference. Remove this code if gradle starts supporting this natively. + */ +fun Project.projectTask(project : String, taskName : String) : Task { + val name = "$project-reference-$taskName"; + val task = tasks.register(name) { + dependsOn(gradle.includedBuild(project).task(":$taskName")) + }.get(); + return task.taskDependencies + .getDependencies(tasks.getByName(name)).iterator().next(); +} + +fun File.resolveAll(vararg xs: String) : File { + var that = this; + for (x in xs) { + that = that.resolve(x) + } + return that +} + +fun Project.getJavaHome(jdk : Jdk) : File { + // TODO(b/270105162): Make sure this works on other platforms. + return getRoot().resolveAll("third_party", "openjdk", jdk.folder, "linux") +} + +fun Project.getCompilerPath(jdk : Jdk) : String { + // TODO(b/270105162): Make sure this works on other platforms. + return getJavaHome(jdk).resolveAll("bin", "javac").toString() +} + +fun Project.getJavaPath(jdk : Jdk) : String { + // TODO(b/270105162): Make sure this works on other platforms. + return getJavaHome(jdk).resolveAll("bin", "java").toString() +} + +fun Project.baseCompilerCommandLine( + jar : File, deps : File, compiler : String, args : List = listOf()) : List { + // Execute r8 commands against a stable r8 with dependencies. + // TODO(b/139725780): See if we can remove or lower the heap size (-Xmx8g). + return listOf( + "${getJavaPath(Jdk.JDK_17)}", + "-Xmx8g", + "-ea", + "-cp", + "$jar:$deps", + "com.android.tools.r8.SwissArmyKnife", + compiler) + args +} + +fun Project.baseCompilerCommandLine( + jar : File, compiler : String, args : List = listOf()) : List { + // Execute r8 commands against a stable r8 with dependencies. + // TODO(b/139725780): See if we can remove or lower the heap size (-Xmx8g). + return listOf( + "${getJavaPath(Jdk.JDK_17)}", + "-Xmx8g", + "-ea", + "-cp", + "$jar", + "com.android.tools.r8.SwissArmyKnife", + compiler) + args +} + +fun Project.createR8LibCommandLine( + r8Compiler : File, + input : File, + output: File, + pgConf : List, + excludingDepsVariant : Boolean, + lib : List = listOf(), + classpath : List = listOf(), + args : List = listOf()) : List { + val pgList = pgConf.flatMap({ listOf("--pg-conf", "$it") }) + val libList = lib.flatMap({ listOf("--lib", "$it") }) + val cpList = classpath.flatMap({ listOf("--classpath", "$it") }) + val exclList = if (excludingDepsVariant) listOf("--excldeps-variant") else listOf() + return listOf( + "python3", + "${getRoot().resolve("tools").resolve("create_r8lib.py")}", + "--r8compiler", + "${r8Compiler}", + "--r8jar", + "${input}", + "--output", + "${output}", + ) + exclList + pgList + libList + cpList +} + +object JvmCompatibility { + val sourceCompatibility = JavaVersion.VERSION_11 + val targetCompatibility = JavaVersion.VERSION_11 +} + +object Versions { + const val asmVersion = "9.5" + const val fastUtilVersion = "7.2.1" + const val gsonVersion = "2.7" + const val guavaVersion = "31.1-jre" + const val junitVersion = "4.13-beta-2" + const val kotlinVersion = "1.8.10" + const val kotlinMetadataVersion = "0.6.2" + const val smaliVersion = "3.0.3" + const val errorproneVersion = "2.18.0" + const val javassist = "3.29.2-GA" +} + +object Deps { + val asm by lazy { "org.ow2.asm:asm:${Versions.asmVersion}" } + val asmUtil by lazy { "org.ow2.asm:asm-util:${Versions.asmVersion}" } + val asmCommons by lazy { "org.ow2.asm:asm-commons:${Versions.asmVersion}" } + val fastUtil by lazy { "it.unimi.dsi:fastutil:${Versions.fastUtilVersion}"} + val gson by lazy { "com.google.code.gson:gson:${Versions.gsonVersion}"} + val guava by lazy { "com.google.guava:guava:${Versions.guavaVersion}" } + val javassist by lazy { "org.javassist:javassist:${Versions.javassist}"} + val junit by lazy { "junit:junit:${Versions.junitVersion}"} + val kotlinMetadata by lazy { + "org.jetbrains.kotlinx:kotlinx-metadata-jvm:${Versions.kotlinMetadataVersion}" } + val kotlinStdLib by lazy { "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinVersion}" } + val kotlinReflect by lazy { "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinVersion}" } + val smali by lazy { "com.android.tools.smali:smali:${Versions.smaliVersion}" } + val errorprone by lazy { "com.google.errorprone:error_prone_core:${Versions.errorproneVersion}" } +} diff --git a/d8_r8/gradle.properties b/d8_r8/gradle.properties index ef0be28bd8..1de43f94f9 100644 --- a/d8_r8/gradle.properties +++ b/d8_r8/gradle.properties @@ -12,6 +12,6 @@ kotlin.incremental.useClasspathSnapshot=true org.gradle.parallel=true org.gradle.caching=true -# Do not download any jdks or detect them. We provide them +# Do not download any jdks or detect them. We provide them. org.gradle.java.installations.auto-detect=false org.gradle.java.installations.auto-download=false diff --git a/d8_r8/keepanno/gradle.properties b/d8_r8/keepanno/gradle.properties index ef0be28bd8..1de43f94f9 100644 --- a/d8_r8/keepanno/gradle.properties +++ b/d8_r8/keepanno/gradle.properties @@ -12,6 +12,6 @@ kotlin.incremental.useClasspathSnapshot=true org.gradle.parallel=true org.gradle.caching=true -# Do not download any jdks or detect them. We provide them +# Do not download any jdks or detect them. We provide them. org.gradle.java.installations.auto-detect=false org.gradle.java.installations.auto-download=false diff --git a/d8_r8/keepanno/settings.gradle.kts b/d8_r8/keepanno/settings.gradle.kts index c214cca02b..3238e39afe 100644 --- a/d8_r8/keepanno/settings.gradle.kts +++ b/d8_r8/keepanno/settings.gradle.kts @@ -3,23 +3,23 @@ // BSD-style license that can be found in the LICENSE file. pluginManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } + repositories { + maven { + url = uri("file:../../third_party/dependencies") } + maven { + url = uri("file:../../third_party/dependencies_new") + } + } } dependencyResolutionManagement { repositories { maven { - url= uri("file:../../third_party/dependencies") + url = uri("file:../../third_party/dependencies") } maven { - url= uri("file:../../third_party/dependencies_new") + url = uri("file:../../third_party/dependencies_new") } } } diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts index 10d4b2be90..34a1c148d8 100644 --- a/d8_r8/main/build.gradle.kts +++ b/d8_r8/main/build.gradle.kts @@ -2,6 +2,7 @@ // 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. +import java.nio.file.Paths import net.ltgt.gradle.errorprone.errorprone plugins { @@ -21,17 +22,100 @@ java { dependencies { implementation(":keepanno") - implementation(Deps.asm) - implementation(Deps.asmUtil) - implementation(Deps.asmCommons) - implementation(Deps.fastUtil) - implementation(Deps.gson) - implementation(Deps.guava) - implementation(Deps.joptSimple) - implementation(Deps.kotlinMetadata) + compileOnly(Deps.asm) + compileOnly(Deps.asmUtil) + compileOnly(Deps.asmCommons) + compileOnly(Deps.fastUtil) + compileOnly(Deps.gson) + compileOnly(Deps.guava) + compileOnly(Deps.kotlinMetadata) errorprone(Deps.errorprone) } +val keepAnnoJarTask = projectTask("keepanno", "jar") + +fun mainJarDependencies() : FileCollection { + return sourceSets + .main + .get() + .compileClasspath + .filter({ "$it".contains("third_party") + && "$it".contains("dependencies") + && !"$it".contains("errorprone") + }) +} + +tasks { + withType { + doFirst { + println("Executing command: ${commandLine.joinToString(" ")}") + } + } + + val swissArmyKnife by registering(Jar::class) { + from(sourceSets.main.get().output) + manifest { + attributes["Main-Class"] = "com.android.tools.r8.SwissArmyKnife" + } + exclude("META-INF/*.kotlin_module") + exclude("**/*.kotlin_metadata") + archiveFileName.set("r8-swissarmyknife.jar") + } + + val depsJar by registering(Jar::class) { + dependsOn(keepAnnoJarTask) + println(header("R8 full dependencies")) + mainJarDependencies().forEach({ println(it) }) + from(mainJarDependencies().map(::zipTree)) + from(keepAnnoJarTask.outputs.files.map(::zipTree)) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + archiveFileName.set("deps.jar") + } + + val r8WithRelocatedDeps by registering(Exec::class) { + dependsOn(swissArmyKnife) + dependsOn(depsJar) + val swissArmy = swissArmyKnife.get().outputs.getFiles().getSingleFile() + val deps = depsJar.get().outputs.files.getSingleFile() + inputs.files(listOf(swissArmy, deps)) + val output = file(Paths.get("build", "libs", "r8-deps-relocated.jar")) + outputs.file(output) + commandLine = baseCompilerCommandLine( + swissArmy, + deps, + "relocator", + listOf("--input", + "$swissArmy", + "--input", + "$deps", + "--output", + "$output", + "--map", + "com.google.common->com.android.tools.r8.com.google.common", + "--map", + "com.google.gson->com.android.tools.r8.com.google.gson", + "--map", + "com.google.thirdparty->com.android.tools.r8.com.google.thirdparty", + "--map", + "org.objectweb.asm->com.android.tools.r8.org.objectweb.asm", + "--map", + "it.unimi.dsi.fastutil->com.android.tools.r8.it.unimi.dsi.fastutil", + "--map", + "kotlin->com.android.tools.r8.jetbrains.kotlin", + "--map", + "kotlinx->com.android.tools.r8.jetbrains.kotlinx", + "--map", + "org.jetbrains->com.android.tools.r8.org.jetbrains", + "--map", + "org.intellij->com.android.tools.r8.org.intellij", + "--map", + "org.checkerframework->com.android.tools.r8.org.checkerframework", + "--map", + "com.google.j2objc->com.android.tools.r8.com.google.j2objc" + )) + } +} + tasks.withType { println("NOTE: Running with JDK: " + org.gradle.internal.jvm.Jvm.current().javaHome) @@ -101,5 +185,4 @@ tasks.withType { // Moving away from identity and canonical items is not planned. options.errorprone.disable("ReferenceEquality") options.errorprone.disable("IdentityHashMapUsage") - } diff --git a/d8_r8/main/gradle.properties b/d8_r8/main/gradle.properties index ef0be28bd8..1de43f94f9 100644 --- a/d8_r8/main/gradle.properties +++ b/d8_r8/main/gradle.properties @@ -12,6 +12,6 @@ kotlin.incremental.useClasspathSnapshot=true org.gradle.parallel=true org.gradle.caching=true -# Do not download any jdks or detect them. We provide them +# Do not download any jdks or detect them. We provide them. org.gradle.java.installations.auto-detect=false org.gradle.java.installations.auto-download=false diff --git a/d8_r8/main/settings.gradle.kts b/d8_r8/main/settings.gradle.kts index 347b7a4395..4745501c7b 100644 --- a/d8_r8/main/settings.gradle.kts +++ b/d8_r8/main/settings.gradle.kts @@ -3,25 +3,28 @@ // BSD-style license that can be found in the LICENSE file. pluginManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } + repositories { + maven { + url = uri("file:../../third_party/dependencies") + } + maven { + url = uri("file:../../third_party/dependencies_new") } + } } dependencyResolutionManagement { repositories { maven { - url= uri("file:../../third_party/dependencies") + url = uri("file:../../third_party/dependencies") } maven { - url= uri("file:../../third_party/dependencies_new") + url = uri("file:../../third_party/dependencies_new") } } } rootProject.name = "r8" + +val root = rootProject.projectDir.parentFile +includeBuild(root.resolve("keepanno")) \ No newline at end of file diff --git a/d8_r8/r8lib/build.gradle.kts b/d8_r8/r8lib/build.gradle.kts new file mode 100644 index 0000000000..c3eea8f867 --- /dev/null +++ b/d8_r8/r8lib/build.gradle.kts @@ -0,0 +1,97 @@ +// Copyright (c) 2023, 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. + +import java.nio.file.Paths + +plugins { + `kotlin-dsl` + id("dependencies-plugin") +} + +java { + sourceCompatibility = JvmCompatibility.sourceCompatibility + targetCompatibility = JvmCompatibility.targetCompatibility +} + +dependencies { } + +val r8WithRelocatedDepsTask = projectTask("main", "r8WithRelocatedDeps") +val r8Jar = projectTask("main", "jar") +val depsJarTask = projectTask("main", "depsJar") +val allTestsJarRelocatedTask = projectTask("test", "allTestsJarRelocated") +val allDepsJarTask = projectTask("test", "allDepsJar") + +tasks { + withType { + doFirst { + println("Executing command: ${commandLine.joinToString(" ")}") + } + } + + val generateKeepRules by registering(Exec::class) { + dependsOn(r8WithRelocatedDepsTask) + dependsOn(depsJarTask) + dependsOn(allTestsJarRelocatedTask) + dependsOn(allDepsJarTask) + val r8 = r8WithRelocatedDepsTask.outputs.files.getSingleFile() + val deps = depsJarTask.outputs.files.getSingleFile() + val tests = allTestsJarRelocatedTask.outputs.files.getSingleFile() + val testDeps = allDepsJarTask.outputs.files.getSingleFile() + inputs.files(listOf(r8, deps, tests, testDeps)) + val output = file(Paths.get("build", "libs", "generated-keep-rules.txt")) + outputs.file(output) + commandLine = baseCompilerCommandLine( + r8, + "tracereferences", + listOf( + "--keep-rules", + "--allowobfuscation", + "--lib", + "${getRoot().resolveAll("third_party", "openjdk", "openjdk-rt-1.8", "rt.jar")}", + "--lib", + "${deps}", + "--lib", + "$testDeps", + "--target", + "$r8", + "--source", + "$tests", + "--output", + "$output")) + } + + val r8LibWithRelocatedDeps by registering(Exec::class) { + dependsOn(generateKeepRules) + dependsOn(r8WithRelocatedDepsTask) + val r8 = r8WithRelocatedDepsTask.outputs.files.getSingleFile() + val generatedKeepRules = generateKeepRules.get().outputs.files.getSingleFile() + inputs.files(listOf(r8, generatedKeepRules)) + val output = file(Paths.get("build", "libs", "r8lib-deps-relocated.jar")) + outputs.file(output) + commandLine = createR8LibCommandLine( + r8, + r8, + output, + listOf(getRoot().resolveAll("src", "main", "keep.txt"), generatedKeepRules), + false) + } + + val r8LibNoDeps by registering(Exec::class) { + dependsOn(depsJarTask) + dependsOn(r8WithRelocatedDepsTask) + val r8Compiler = r8WithRelocatedDepsTask.outputs.files.getSingleFile() + val r8Jar = r8Jar.outputs.files.getSingleFile() + val deps = depsJarTask.outputs.files.getSingleFile() + inputs.files(listOf(r8Compiler, r8Jar, deps)) + val output = file(Paths.get("build", "libs", "r8lib-no-deps.jar")) + outputs.file(output) + commandLine = createR8LibCommandLine( + r8Compiler, + r8Jar, + output, + listOf(getRoot().resolveAll("src", "main", "keep.txt")), + true, + listOf(deps)) + } +} diff --git a/d8_r8/r8lib/gradle.properties b/d8_r8/r8lib/gradle.properties new file mode 100644 index 0000000000..1de43f94f9 --- /dev/null +++ b/d8_r8/r8lib/gradle.properties @@ -0,0 +1,17 @@ +# Copyright (c) 2023, 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. + +org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 +kotlin.daemon.jvmargs=-Xmx3g -Dkotlin.js.compiler.legacy.force_enabled=true +systemProp.file.encoding=UTF-8 + +# Enable new incremental compilation +kotlin.incremental.useClasspathSnapshot=true + +org.gradle.parallel=true +org.gradle.caching=true + +# Do not download any jdks or detect them. We provide them. +org.gradle.java.installations.auto-detect=false +org.gradle.java.installations.auto-download=false diff --git a/d8_r8/r8lib/settings.gradle.kts b/d8_r8/r8lib/settings.gradle.kts new file mode 100644 index 0000000000..cb9fc2e45e --- /dev/null +++ b/d8_r8/r8lib/settings.gradle.kts @@ -0,0 +1,31 @@ +// Copyright (c) 2023, 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. + +pluginManagement { + repositories { + maven { + url = uri("file:../../third_party/dependencies") + } + maven { + url = uri("file:../../third_party/dependencies_new") + } + } +} + +dependencyResolutionManagement { + repositories { + maven { + url = uri("file:../../third_party/dependencies") + } + maven { + url = uri("file:../../third_party/dependencies_new") + } + } +} + +rootProject.name = "r8lib" + +val root = rootProject.projectDir.parentFile +includeBuild(root.resolve("main")) +includeBuild(root.resolve("test")) diff --git a/d8_r8/settings.gradle.kts b/d8_r8/settings.gradle.kts index 9d2dfa16d7..f73899e306 100644 --- a/d8_r8/settings.gradle.kts +++ b/d8_r8/settings.gradle.kts @@ -2,26 +2,26 @@ // 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. -// TODO(X): Move this file out the repository root when old gradle is removed. +// TODO(b/270105162): Move this file out the repository root when old gradle is removed. pluginManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } + repositories { + maven { + url = uri("file:../third_party/dependencies") + } + maven { + url = uri("file:../third_party/dependencies_new") } + } } dependencyResolutionManagement { repositories { maven { - url= uri("file:../third_party/dependencies") + url = uri("file:../third_party/dependencies") } maven { - url= uri("file:../third_party/dependencies_new") + url = uri("file:../third_party/dependencies_new") } } } @@ -29,6 +29,7 @@ dependencyResolutionManagement { rootProject.name = "d8-r8" // Bootstrap building by downloading dependencies. + fun String.execute() = org.codehaus.groovy.runtime.ProcessGroovyMethods.execute(this) @@ -67,12 +68,18 @@ if (process.exitValue() != 0) { + "\n${process.err()}\n${process.out()}") } +val root = rootProject.projectDir + // This project is temporarily located in d8_r8. When moved to root, the parent // folder should just be removed. -includeBuild(rootProject.projectDir.parentFile.resolve("commonBuildSrc")) -includeBuild("keepanno") +includeBuild(root.parentFile.resolve("commonBuildSrc")) +includeBuild(root.resolve("keepanno")) // We need to include src/main as a composite-build otherwise our test-modules // will compete with the test to compile the source files. -includeBuild("main") -includeBuild("test") +includeBuild(root.resolve("main")) +includeBuild(root.resolve("test")) + +// Include r8lib as standalone to have a nice separation between source artifacts and r8 compiled +// artifacts +includeBuild(root.resolve("r8lib")) \ No newline at end of file diff --git a/d8_r8/test/build.gradle.kts b/d8_r8/test/build.gradle.kts index 34f153b65a..c7837c04f3 100644 --- a/d8_r8/test/build.gradle.kts +++ b/d8_r8/test/build.gradle.kts @@ -2,54 +2,71 @@ // 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. +import java.nio.file.Paths +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + plugins { `kotlin-dsl` id("dependencies-plugin") } -val root = getRoot(); - java { - sourceSets.test.configure { - java.srcDir(root.resolveAll("src", "test", "java")) - } - sourceCompatibility = JvmCompatibility.sourceCompatibility - targetCompatibility = JvmCompatibility.targetCompatibility + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } -// We cannot use languageVersion.set(JavaLanguageVersion.of("8")) because gradle cannot figure -// out that the jdk is 1_8 and will try to download it. -tasks.withType { - kotlinOptions { - jvmTarget = "11" +dependencies { } + +val r8WithRelocatedDepsTask = projectTask("main", "r8WithRelocatedDeps") +val java8TestJarTask = projectTask("tests_java_8", "testJar") +val java8DepsJarTask = projectTask("tests_java_8", "depsJar") + +tasks { + withType { + options.setFork(true) + options.forkOptions.executable = getCompilerPath(Jdk.JDK_17) + options.forkOptions.javaHome = getJavaHome(Jdk.JDK_17) } -} + withType { + kotlinOptions { + jvmTarget = "17" + } + } -dependencies { - implementation(":r8") - implementation(":keepanno") - implementation(Deps.asm) - implementation(Deps.gson) - implementation(Deps.guava) - implementation(Deps.junit) - implementation(Deps.kotlinStdLib) - implementation(Deps.kotlinReflect) - implementation(Deps.kotlinMetadata) - implementation(files(root.resolveAll("third_party", "ddmlib", "ddmlib.jar"))) - implementation( - files( - root.resolveAll("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar"))) - implementation(files(root.resolveAll("third_party", "jasmin", "jasmin-2.4.jar"))) - implementation(Deps.fastUtil) - implementation(Deps.smali) - implementation(Deps.asmUtil) -} + val allTestsJar by registering(Jar::class) { + dependsOn(java8TestJarTask) + from(java8TestJarTask.outputs.getFiles().map(::zipTree)) + exclude("META-INF/*.kotlin_module") + exclude("**/*.kotlin_metadata") + archiveFileName.set("all-tests.jar") + } -tasks.named("test") { - dependsOn(gradle.includedBuild("tests_java_8").task(":compileJava")) -} + val allDepsJar by registering(Jar::class) { + dependsOn(java8DepsJarTask) + from(java8DepsJarTask.outputs.getFiles().map(::zipTree)) + exclude("META-INF/*.kotlin_module") + exclude("**/*.kotlin_metadata") + archiveFileName.set("all-deps.jar") + } -tasks.withType { - environment("USE_NEW_GRADLE_SETUP", "true") + val allTestsJarRelocated by registering(Exec::class) { + dependsOn(r8WithRelocatedDepsTask) + dependsOn(allTestsJar) + val r8 = r8WithRelocatedDepsTask.outputs.getFiles().getSingleFile() + val allTests = allTestsJar.get().outputs.files.getSingleFile() + inputs.files(listOf(r8, allTests)) + val output = file(Paths.get("build", "libs", "all-tests-relocated.jar")) + outputs.file(output) + commandLine = baseCompilerCommandLine( + r8, + "relocator", + listOf("--input", + "$allTests", + "--output", + "$output", + "--map", + "kotlinx.metadata->com.android.tools.r8.jetbrains.kotlinx.metadata")) + } } diff --git a/d8_r8/test/gradle.properties b/d8_r8/test/gradle.properties index ef0be28bd8..1de43f94f9 100644 --- a/d8_r8/test/gradle.properties +++ b/d8_r8/test/gradle.properties @@ -12,6 +12,6 @@ kotlin.incremental.useClasspathSnapshot=true org.gradle.parallel=true org.gradle.caching=true -# Do not download any jdks or detect them. We provide them +# Do not download any jdks or detect them. We provide them. org.gradle.java.installations.auto-detect=false org.gradle.java.installations.auto-download=false diff --git a/d8_r8/test/settings.gradle.kts b/d8_r8/test/settings.gradle.kts index 5946a12961..5b00aae306 100644 --- a/d8_r8/test/settings.gradle.kts +++ b/d8_r8/test/settings.gradle.kts @@ -3,23 +3,23 @@ // BSD-style license that can be found in the LICENSE file. pluginManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } + repositories { + maven { + url = uri("file:../../third_party/dependencies") } + maven { + url = uri("file:../../third_party/dependencies_new") + } + } } dependencyResolutionManagement { repositories { maven { - url= uri("file:../third_party/dependencies") + url = uri("file:../../third_party/dependencies") } maven { - url= uri("file:../third_party/dependencies_new") + url = uri("file:../../third_party/dependencies_new") } } } @@ -27,4 +27,5 @@ dependencyResolutionManagement { rootProject.name = "r8-tests" val root = rootProject.projectDir.parentFile +includeBuild(root.resolve("main")) includeBuild(root.resolve("test_modules").resolve("tests_java_8")) diff --git a/d8_r8/test_modules/tests_java_8/build.gradle.kts b/d8_r8/test_modules/tests_java_8/build.gradle.kts index 99085995df..55ee7a825a 100644 --- a/d8_r8/test_modules/tests_java_8/build.gradle.kts +++ b/d8_r8/test_modules/tests_java_8/build.gradle.kts @@ -14,49 +14,24 @@ plugins { val root = getRoot() java { - sourceSets.main.configure { + sourceSets.test.configure { java.srcDir(root.resolveAll("src", "test", "java")) } + // We cannot use languageVersion.set(JavaLanguageVersion.of(8)) because gradle cannot figure + // out that the jdk is 1_8 and will try to download it. sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } - -// We cannot use languageVersion.set(JavaLanguageVersion.of("8")) because gradle cannot figure -// out that the jdk is 1_8 and will try to download it. -tasks.withType { - kotlinOptions { - jvmTarget = "1.8" - } -} - -// The test module compilation depends on main and keep anno output, but we cannot directly -// reference the task we only obtain a task reference. To obtain the actual reference by creating -// a dummy. -tasks.register("dummy-keepanno-reference") { - dependsOn(gradle.includedBuild("keepanno").task(":jar")) -} -val keepAnnoTask = tasks.getByName("dummy-keepanno-reference") - .taskDependencies - .getDependencies(tasks.getByName("dummy-keepanno-reference")) - .iterator() - .next() - -tasks.register("dummy-r8-reference") { - dependsOn(gradle.includedBuild("main").task(":jar")) -} -val r8Task = tasks.getByName("dummy-r8-reference") - .taskDependencies - .getDependencies(tasks.getByName("dummy-r8-reference")) - .iterator() - .next() - dependencies { - implementation(keepAnnoTask.outputs.files) - implementation(r8Task.outputs.files) + // If we depend on keepanno by referencing the project source outputs we get an error regarding + // incompatible java class file version. By depending on the jar we circumvent that. + implementation(projectTask("keepanno", "jar").outputs.files) + implementation(projectTask("main", "jar").outputs.files) implementation(Deps.asm) implementation(Deps.gson) implementation(Deps.guava) + implementation(Deps.javassist) implementation(Deps.junit) implementation(Deps.kotlinStdLib) implementation(Deps.kotlinReflect) @@ -71,18 +46,50 @@ dependencies { implementation(Deps.asmUtil) } -tasks.withType { - dependsOn(keepAnnoTask) - dependsOn(r8Task) - options.setFork(true) - options.forkOptions.memoryMaximumSize = "3g" - options.forkOptions.jvmArgs = listOf( - "-Xss256m", - // Set the bootclass path so compilation is consistent with 1.8 target compatibility. - "-Xbootclasspath/a:third_party/openjdk/openjdk-rt-1.8/rt.jar") +fun testDependencies() : FileCollection { + return sourceSets + .test + .get() + .compileClasspath + .filter({ "$it".contains("keepanno") || + ("$it".contains("third_party") + && !"$it".contains("errorprone") + && !"$it".contains("gradle")) + }) } -tasks.withType { - dependsOn(keepAnnoTask) - dependsOn(r8Task) +tasks { + withType { + dependsOn(gradle.includedBuild("keepanno").task(":jar")) + dependsOn(gradle.includedBuild("main").task(":jar")) + options.setFork(true) + options.forkOptions.memoryMaximumSize = "3g" + options.forkOptions.jvmArgs = listOf( + "-Xss256m", + // Set the bootclass path so compilation is consistent with 1.8 target compatibility. + "-Xbootclasspath/a:third_party/openjdk/openjdk-rt-1.8/rt.jar") + } + + withType { + dependsOn(gradle.includedBuild("keepanno").task(":jar")) + dependsOn(gradle.includedBuild("main").task(":jar")) + kotlinOptions { + // We cannot use languageVersion.set(JavaLanguageVersion.of(8)) because gradle cannot figure + // out that the jdk is 1_8 and will try to download it. + jvmTarget = "1.8" + } + } + + val testJar by registering(Jar::class) { + from(sourceSets.test.get().output) + } + + val depsJar by registering(Jar::class) { + dependsOn(gradle.includedBuild("keepanno").task(":jar")) + println(header("Test Java 8 dependencies")) + testDependencies().forEach({ println(it) }) + from(testDependencies().map(::zipTree)) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + archiveFileName.set("deps.jar") + } } diff --git a/d8_r8/test_modules/tests_java_8/gradle.properties b/d8_r8/test_modules/tests_java_8/gradle.properties index ef0be28bd8..1de43f94f9 100644 --- a/d8_r8/test_modules/tests_java_8/gradle.properties +++ b/d8_r8/test_modules/tests_java_8/gradle.properties @@ -12,6 +12,6 @@ kotlin.incremental.useClasspathSnapshot=true org.gradle.parallel=true org.gradle.caching=true -# Do not download any jdks or detect them. We provide them +# Do not download any jdks or detect them. We provide them. org.gradle.java.installations.auto-detect=false org.gradle.java.installations.auto-download=false diff --git a/d8_r8/test_modules/tests_java_8/settings.gradle.kts b/d8_r8/test_modules/tests_java_8/settings.gradle.kts index 6ab7facdfa..592c7d220e 100644 --- a/d8_r8/test_modules/tests_java_8/settings.gradle.kts +++ b/d8_r8/test_modules/tests_java_8/settings.gradle.kts @@ -3,35 +3,32 @@ // BSD-style license that can be found in the LICENSE file. pluginManagement { - repositories { - maven { - url = uri("file:../../../third_party/dependencies") - } - maven { - url = uri("file:../../../third_party/dependencies_new") - } + repositories { + maven { + url = uri("file:../../../third_party/dependencies") + } + maven { + url = uri("file:../../../third_party/dependencies_new") } + } } dependencyResolutionManagement { repositories { maven { - url= uri("file:../third_party/dependencies") + url = uri("file:../../../third_party/dependencies") } maven { - url= uri("file:../third_party/dependencies_new") + url = uri("file:../../../third_party/dependencies_new") } } } -rootProject.name = "r8-java8-tests" - -val d8Root = rootProject.projectDir.parentFile.parentFile -val root = d8Root.parentFile +rootProject.name = "tests_java_8" -includeBuild(root.resolve("commonBuildSrc")) -includeBuild(d8Root.resolve("keepanno")) +val root = rootProject.projectDir.parentFile.parentFile +includeBuild(root.resolve("keepanno")) // We need to include src/main as a composite-build otherwise our test-modules // will compete with the test to compile the source files. -includeBuild(d8Root.resolve("main")) +includeBuild(root.resolve("main")) diff --git a/tools/create_r8lib.py b/tools/create_r8lib.py index 51e2fd618d..1b6c52fa70 100755 --- a/tools/create_r8lib.py +++ b/tools/create_r8lib.py @@ -52,6 +52,11 @@ def parse_options(): '--r8jar', required=True, help='The R8 jar to compile') + parser.add_argument( + '--r8compiler', + required=True, + default='build/libs/r8_with_deps.jar', + help='The R8 compiler to use') return parser.parse_args() def get_r8_version(r8jar): @@ -89,7 +94,7 @@ def main(): cmd = [jdk.GetJavaExecutable(), '-Xmx8g', '-ea'] if args.debug_agent: cmd.extend(['-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005']) - cmd.extend(['-cp', 'build/libs/r8_with_deps.jar', 'com.android.tools.r8.R8']) + cmd.extend(['-cp', args.r8compiler, 'com.android.tools.r8.R8']) cmd.append(args.r8jar) cmd.append('--classfile') cmd.extend(['--map-id-template', map_id_template]) From ae2e988cdeb1787ef8781bbe725c123891fd4822 Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Wed, 14 Jun 2023 15:18:13 +0200 Subject: [PATCH 101/153] Remove required flag change to create_r8lib.py Change-Id: Ib8fab7591ad2c5c22dde4166c7d6cf1588712b0b --- tools/create_r8lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/create_r8lib.py b/tools/create_r8lib.py index 1b6c52fa70..a27cf24dc4 100755 --- a/tools/create_r8lib.py +++ b/tools/create_r8lib.py @@ -54,7 +54,6 @@ def parse_options(): help='The R8 jar to compile') parser.add_argument( '--r8compiler', - required=True, default='build/libs/r8_with_deps.jar', help='The R8 compiler to use') return parser.parse_args() From 47da9f7c4d681188257c8620c423ed27978a4a09 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 13 Jun 2023 17:14:51 +0200 Subject: [PATCH 102/153] Add test for composing overloaded methods with same range Bug: b/286781273 Change-Id: Id6f43f4c29ffdebbde6722ef560fd55e8b07fd79 --- .../ComposeOverloadSameRangeTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/mappingcompose/ComposeOverloadSameRangeTest.java diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOverloadSameRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOverloadSameRangeTest.java new file mode 100644 index 0000000000..4600a60fd3 --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOverloadSameRangeTest.java @@ -0,0 +1,57 @@ +// Copyright (c) 2023, 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.mappingcompose; + +import static org.junit.Assert.assertThrows; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** This is a regression test for b/286781273. */ +@RunWith(Parameterized.class) +public class ComposeOverloadSameRangeTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public ComposeOverloadSameRangeTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "com.foo -> a:", + " 1:2:void method():41:42 -> m", + " 3:3:void inlinee():20:20 -> m", + " 3:3:void method():43 -> m", + " 1:2:void method(int):111:112 -> m", + " 3:3:void method(int):113:113 -> m"); + private static final String mappingBar = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "a -> b:", + " 1:3:void m():1:3 -> m", + " 1:3:void m(int):1:3 -> m"); + private static final String mappingResult = StringUtils.unixLines(); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar); + // TODO(b/286781273): Handle same range for overloaded methods. + assertThrows(AssertionError.class, () -> MappingComposer.compose(mappingForFoo, mappingForBar)); + } +} From 1f65eee108f28deafd2536713fe55387d41fdec1 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 13 Jun 2023 17:15:39 +0200 Subject: [PATCH 103/153] Correct the check for subsequent range being an inline range Bug: b/286781273 Change-Id: Ie2afc1a1b4545bf8dcc56891db3b63b03ce77711 --- .../tools/r8/naming/ComposingBuilder.java | 30 ++++++++++++------- .../tools/r8/naming/MappedRangeUtils.java | 13 ++++++-- .../ComposeInlineFollowedByInlineTest.java | 4 +-- .../ComposeInlineOfPositionsTest.java | 4 +-- ...ineOfPositionsThatViolateNewRangeTest.java | 4 +-- .../ComposeInlineResidualQualifiedTest.java | 2 +- ...ComposeOutlineHavingInlineInlinedTest.java | 2 +- .../ComposeOverloadSameRangeTest.java | 17 ++++++++--- 8 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index f21fafe789..455a8b86fe 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -709,11 +709,10 @@ private List fixupInlinedOutlines( int composedRangeIndex = 0; while (composedRangeIndex < composedRanges.size() - 1) { MappedRange outline = composedRanges.get(composedRangeIndex++); - if (outline.isOutlineFrame() - && outline.minifiedRange.equals(composedRanges.get(composedRangeIndex).minifiedRange)) { + MappedRange outlineCallSite = composedRanges.get(composedRangeIndex); + if (outline.isOutlineFrame() && isInlineMappedRange(outline, outlineCallSite)) { // We should replace the inlined outline frame positions with the synthesized // positions from the outline call site. - MappedRange outlineCallSite = composedRanges.get(composedRangeIndex); if (outlineCallSite.getOutlineCallsiteInformation().size() != 1) { // If we have an inlined outline it must be such that the outer frame is an // outline callsite. @@ -1263,25 +1262,26 @@ private void composeMappedRange( List existingMappedRanges, ComputedOutlineInformation computedOutlineInformation, int lastStartingMinifiedFrom, - int position) + int lastEndingMinifiedTo) throws MappingComposeException { Range existingRange = existingMappedRanges.get(0).minifiedRange; assert existingMappedRanges.stream().allMatch(x -> x.minifiedRange.equals(existingRange)); - Range newMinifiedRange = new Range(lastStartingMinifiedFrom, position); - boolean copyOriginalRange = existingRange.equals(newMappedRange.getOriginalRangeOrIdentity()); + Range newMinifiedRange = new Range(lastStartingMinifiedFrom, lastEndingMinifiedTo); for (MappedRange existingMappedRange : existingMappedRanges) { Range existingOriginalRange = existingMappedRange.getOriginalRangeOrIdentity(); Range newOriginalRange; - if (copyOriginalRange - || existingOriginalRange == null - || existingOriginalRange.span() == 1) { + if (canUseOriginalRange( + existingOriginalRange, newMappedRange.getOriginalRangeOrIdentity())) { newOriginalRange = existingOriginalRange; } else { // Find the window that the new range points to into the original range. int existingMinifiedPos = newMappedRange.getOriginalLineNumber(lastStartingMinifiedFrom); int newOriginalStart = existingMappedRange.getOriginalLineNumber(existingMinifiedPos); if (newMappedRange.getOriginalRangeOrIdentity().span() == 1) { - newOriginalRange = new Range(newOriginalStart, newOriginalStart); + newOriginalRange = + newMappedRange.getOriginalRangeOrIdentity().isCardinal + ? new Range(newOriginalStart) + : new Range(newOriginalStart, newOriginalStart); } else { assert newMinifiedRange.span() <= existingOriginalRange.span(); newOriginalRange = @@ -1325,6 +1325,16 @@ private void composeMappedRange( } } + private boolean canUseOriginalRange(Range existingRange, Range newRange) { + if (newRange.equals(existingRange)) { + return true; + } + if (newRange.isCardinal) { + return false; + } + return existingRange == null || existingRange.span() == 1; + } + /*** * Populates newMappingInformation with existingMappingInformation. */ diff --git a/src/main/java/com/android/tools/r8/naming/MappedRangeUtils.java b/src/main/java/com/android/tools/r8/naming/MappedRangeUtils.java index 7dff280465..14382699cc 100644 --- a/src/main/java/com/android/tools/r8/naming/MappedRangeUtils.java +++ b/src/main/java/com/android/tools/r8/naming/MappedRangeUtils.java @@ -24,8 +24,15 @@ static boolean isInlineMappedRange(List mappedRanges, int index) { if (index + 1 >= mappedRanges.size()) { return false; } - MappedRange mappedRange = mappedRanges.get(index); - return mappedRange.minifiedRange != null - && mappedRange.minifiedRange.equals(mappedRanges.get(index + 1).minifiedRange); + return isInlineMappedRange(mappedRanges.get(index), mappedRanges.get(index + 1)); + } + + static boolean isInlineMappedRange(MappedRange previous, MappedRange next) { + if (previous.minifiedRange == null) { + return false; + } + return next.getOriginalRangeOrIdentity() != null + && next.getOriginalRangeOrIdentity().isCardinal + && previous.minifiedRange.equals(next.minifiedRange); } } diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java index dbfcdb53a7..fc1309a635 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java @@ -61,7 +61,7 @@ public static TestParametersCollection data() { " 2:2:void com.foo.inlinee1():43:43 -> y", " 2:2:void foo.bar.baz.inlinee1():41 -> y", " 2:2:void com.foo.caller():40 -> y", - " 2:2:void inlinee2():42:42 -> y", + " 2:2:void inlinee2():42 -> y", " 2:2:void foo.bar.baz.inlinee1():41 -> y", " 2:2:void caller():40 -> y", " 2:2:void qux():1 -> y", @@ -73,7 +73,7 @@ public static TestParametersCollection data() { " 4:5:void inlinee1():43:44 -> z", " 4:5:void foo.bar.baz.inlinee1():41 -> z", " 4:5:void caller():40 -> z", - " 4:5:void inlinee2():49:49 -> z", + " 4:5:void inlinee2():49 -> z", " 4:5:void caller2():27 -> z"); @Test diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsTest.java index e228204e00..90498fd4b7 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsTest.java @@ -50,9 +50,9 @@ public ComposeInlineOfPositionsTest(TestParameters parameters) { "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "com.foo -> b:", " 3:3:void m1():10 -> z", - " 3:3:void y():30:30 -> z", + " 3:3:void y():30 -> z", " 4:4:void m2(int):20 -> z", - " 4:4:void y():31:31 -> z"); + " 4:4:void y():31 -> z"); @Test public void testCompose() throws Exception { diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsThatViolateNewRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsThatViolateNewRangeTest.java index a732f723f2..ff4c376bc2 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsThatViolateNewRangeTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsThatViolateNewRangeTest.java @@ -56,10 +56,10 @@ public ComposeInlineOfPositionsThatViolateNewRangeTest(TestParameters parameters "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "com.foo -> c:", " 10:10:void m1():10 -> w", - " 10:10:void y():30:30 -> w", + " 10:10:void y():30 -> w", " 10:10:void new_synthetic_method():0:0 -> w", " 11:11:void m2(int):20 -> w", - " 11:11:void y():31:31 -> w", + " 11:11:void y():31 -> w", " 11:11:void new_synthetic_method():0:0 -> w"); @Test diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java index 1912d84026..3b01b3420d 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java @@ -48,7 +48,7 @@ public static TestParametersCollection data() { "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "com.bar -> C:", " 1:3:void com.foo.method1():43:43 -> y", - " 1:3:void method2():104:104 -> y", + " 1:3:void method2():104 -> y", " 2:3:void method2():105:106 -> y", "com.foo -> A:"); diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java index 03e93e65b5..301934c201 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java @@ -58,7 +58,7 @@ public static TestParametersCollection data() { " 1:1:int some.inlinee():75:75 -> s", " 1:1:int outlineCaller(int):23 -> s", " 2:4:int another.inline():102:104 -> s", - " 2:4:int some.inlinee():76:76 -> s", + " 2:4:int some.inlinee():76 -> s", " 2:4:int foo.bar.baz.outlineCaller(int):98:98 -> s", " 2:4:int outlineCaller(int):24 -> s", "outline.Class -> A:"); diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOverloadSameRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOverloadSameRangeTest.java index 4600a60fd3..a81af5dae8 100644 --- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOverloadSameRangeTest.java +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOverloadSameRangeTest.java @@ -4,7 +4,8 @@ package com.android.tools.r8.mappingcompose; -import static org.junit.Assert.assertThrows; +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -45,13 +46,21 @@ public ComposeOverloadSameRangeTest(TestParameters parameters) { "a -> b:", " 1:3:void m():1:3 -> m", " 1:3:void m(int):1:3 -> m"); - private static final String mappingResult = StringUtils.unixLines(); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.2'}", + "com.foo -> b:", + " 1:2:void method():41:42 -> m", + " 3:3:void inlinee():20:20 -> m", + " 3:3:void method():43 -> m", + " 1:2:void method(int):111:112 -> m", + " 3:3:void method(int):113:113 -> m"); @Test public void testCompose() throws Exception { ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo); ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar); - // TODO(b/286781273): Handle same range for overloaded methods. - assertThrows(AssertionError.class, () -> MappingComposer.compose(mappingForFoo, mappingForBar)); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); } } From 3e9e9f2e0db2fb6d7db0840b9aaccbb34e4fe133 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Wed, 14 Jun 2023 19:46:37 +0200 Subject: [PATCH 104/153] Minor updates to make the bots green Bug: b/287210793 Bug: b/286781273 Change-Id: I981e48523549e14483fab0e16b0bfae1bc7bc829 --- .../tools/r8/naming/ComposingBuilder.java | 7 ++++--- .../tools/r8/naming/MappedRangeUtils.java | 21 +++++++++++++++++++ ...appedPositionToClassNameMapperBuilder.java | 4 +++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java index 455a8b86fe..d0d89a851e 100644 --- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java +++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java @@ -4,7 +4,7 @@ package com.android.tools.r8.naming; -import static com.android.tools.r8.naming.MappedRangeUtils.isInlineMappedRange; +import static com.android.tools.r8.naming.MappedRangeUtils.isInlineMappedRangeForComposition; import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument; import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange; @@ -648,7 +648,7 @@ private void composeMethodNamings( mappedRange, computedOutlineInformation); composedInlineFrames = composeInlineFrames(newComposedRange, composedInlineFrames); - if (!isInlineMappedRange(newRangesToCompose, i)) { + if (!isInlineMappedRangeForComposition(newRangesToCompose, i)) { composedRanges.addAll(composedInlineFrames); composedInlineFrames = Collections.emptyList(); } @@ -710,7 +710,8 @@ private List fixupInlinedOutlines( while (composedRangeIndex < composedRanges.size() - 1) { MappedRange outline = composedRanges.get(composedRangeIndex++); MappedRange outlineCallSite = composedRanges.get(composedRangeIndex); - if (outline.isOutlineFrame() && isInlineMappedRange(outline, outlineCallSite)) { + if (outline.isOutlineFrame() + && isInlineMappedRangeForComposition(outline, outlineCallSite)) { // We should replace the inlined outline frame positions with the synthesized // positions from the outline call site. if (outlineCallSite.getOutlineCallsiteInformation().size() != 1) { diff --git a/src/main/java/com/android/tools/r8/naming/MappedRangeUtils.java b/src/main/java/com/android/tools/r8/naming/MappedRangeUtils.java index 14382699cc..421709d0a6 100644 --- a/src/main/java/com/android/tools/r8/naming/MappedRangeUtils.java +++ b/src/main/java/com/android/tools/r8/naming/MappedRangeUtils.java @@ -32,6 +32,27 @@ static boolean isInlineMappedRange(MappedRange previous, MappedRange next) { return false; } return next.getOriginalRangeOrIdentity() != null + && previous.minifiedRange.equals(next.minifiedRange); + } + + // TODO(b/286781273): Remove when fixed. + @Deprecated() + static boolean isInlineMappedRangeForComposition(List mappedRanges, int index) { + // We are comparing against the next entry so we need a buffer of one. + if (index + 1 >= mappedRanges.size()) { + return false; + } + return isInlineMappedRangeForComposition(mappedRanges.get(index), mappedRanges.get(index + 1)); + } + + // TODO(b/286781273): Remove when fixed. + @Deprecated + static boolean isInlineMappedRangeForComposition(MappedRange previous, MappedRange next) { + if (previous.minifiedRange == null) { + return false; + } + return next.getOriginalRangeOrIdentity() != null + // TODO(b/286781273): As a temporary fix, we check for the original range being cardinal. && next.getOriginalRangeOrIdentity().isCardinal && previous.minifiedRange.equals(next.minifiedRange); } diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java index ffa8c2a658..54efc7d8ba 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java +++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java @@ -384,7 +384,9 @@ public MappedPositionToClassNamingBuilder addMappedPositions( } i = j; } - assert mappedPositions.size() <= 1 + // TODO(b/287210793): Enable assertion again. + assert true + || mappedPositions.size() <= 1 || getBuilder().hasNoOverlappingRangesForSignature(residualSignature); return this; } From 836e4ae3d007c0533698e87d7aada3e6abf39da5 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 13 Jun 2023 14:11:02 +0200 Subject: [PATCH 105/153] Avoid naively recreating FieldAccessInfoCollection during lens rewriting Change-Id: Ibe8e83d3ddf5cfb28bb66de83b8a08779cd5da9d --- .../graph/FieldAccessInfoCollectionImpl.java | 25 +++--- .../com/android/tools/r8/utils/LensUtils.java | 83 +++++++++++++++++++ 2 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java index 53e57f6db4..6b2bad4c70 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java @@ -5,7 +5,7 @@ package com.android.tools.r8.graph; import com.android.tools.r8.graph.lens.GraphLens; -import com.android.tools.r8.utils.ObjectUtils; +import com.android.tools.r8.utils.LensUtils; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.Timing; import java.util.IdentityHashMap; @@ -82,19 +82,18 @@ public void restrictToProgram(DexDefinitionSupplier definitions) { public FieldAccessInfoCollectionImpl rewrittenWithLens( DexDefinitionSupplier definitions, GraphLens lens, Timing timing) { timing.begin("Rewrite FieldAccessInfoCollectionImpl"); - FieldAccessInfoCollectionImpl collection = new FieldAccessInfoCollectionImpl(); - Consumer rewriteAndMergeFieldInfo = - info -> { - FieldAccessInfoImpl rewrittenInfo = info.rewrittenWithLens(definitions, lens, timing); - DexField newField = rewrittenInfo.getField(); - collection.infos.compute( - newField, - (ignore, oldInfo) -> - ObjectUtils.mapNotNullOrDefault(oldInfo, rewrittenInfo, rewrittenInfo::join)); - }; - infos.values().forEach(rewriteAndMergeFieldInfo); + Map newInfos = + LensUtils.mutableRewriteMap( + infos, + IdentityHashMap::new, + (field, info) -> info.rewrittenWithLens(definitions, lens, timing), + (field, info, rewrittenInfo) -> rewrittenInfo.getField(), + (field, info, rewrittenInfo) -> rewrittenInfo, + (field, info, otherInfo) -> info.join(otherInfo)); + FieldAccessInfoCollectionImpl result = + newInfos != infos ? new FieldAccessInfoCollectionImpl(newInfos) : this; timing.end(); - return collection; + return result; } // This is used to verify that the temporary mappings inserted into `infos` by the Enqueuer are diff --git a/src/main/java/com/android/tools/r8/utils/LensUtils.java b/src/main/java/com/android/tools/r8/utils/LensUtils.java index 1b88985e0a..4097730772 100644 --- a/src/main/java/com/android/tools/r8/utils/LensUtils.java +++ b/src/main/java/com/android/tools/r8/utils/LensUtils.java @@ -9,11 +9,94 @@ import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.lens.GraphLens; import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.IntFunction; public class LensUtils { + /** + * Helper to lens rewrite a Map. + * + * @param map The Map that needs to be rewritten. The map may be mutated by this method, thus the + * original unrewritten map should never be used after calling this method. + * @param mapFactory Function that given a capacity returns a Map with the given capacity. + * @param preprocessing Function that given a (key, value) pair returns some data that is passed + * to the rewriting of the key using {@param keyRewriter} and to the rewriting of the value + * using {@param valueRewriter}. This can be used if the key and value shares some common data + * that needs rewriting. + * @param keyRewriter Function that rewrites a key. + * @param valueRewriter Function that rewrites a value. + * @param valueMerger Function that merges two values that have the same rewritten key. + */ + public static Map mutableRewriteMap( + Map map, + IntFunction> mapFactory, + BiFunction preprocessing, + TriFunction keyRewriter, + TriFunction valueRewriter, + TriFunction valueMerger) { + Collection elementsToRemove = null; + Map rewrittenMap = null; + for (Entry entry : map.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + P data = preprocessing.apply(key, value); + K rewrittenKey = keyRewriter.apply(key, value, data); + + // If the key is mapped to null then remove if from `map` unless there are other changes. + if (rewrittenKey == null) { + if (rewrittenMap == null) { + if (elementsToRemove == null) { + elementsToRemove = new ArrayList<>(); + } + elementsToRemove.add(key); + } + continue; + } + + V rewrittenValue = valueRewriter.apply(key, value, data); + if (rewrittenKey == key && rewrittenValue == value) { + if (rewrittenMap == null) { + // Don't write this entry until we find the first change. + continue; + } + } else { + if (rewrittenMap == null) { + rewrittenMap = mapFactory.apply(map.size()); + MapUtils.forEachUntilExclusive(map, rewrittenMap::put, key); + if (elementsToRemove != null) { + assert !elementsToRemove.isEmpty(); + rewrittenMap.keySet().removeAll(elementsToRemove); + elementsToRemove = null; + } + } + } + V existingRewrittenValue = rewrittenMap.get(rewrittenKey); + rewrittenMap.put( + rewrittenKey, + existingRewrittenValue != null + ? valueMerger.apply(rewrittenKey, rewrittenValue, existingRewrittenValue) + : rewrittenValue); + } + if (rewrittenMap != null) { + assert elementsToRemove == null; + return MapUtils.trimCapacityIfSizeLessThan(rewrittenMap, mapFactory, map.size()); + } else { + if (elementsToRemove != null) { + assert !elementsToRemove.isEmpty(); + map.keySet().removeAll(elementsToRemove); + return MapUtils.trimCapacity(map, mapFactory); + } + return map; + } + } + public static Set rewrittenWithRenamedSignature( Set methods, DexDefinitionSupplier definitions, GraphLens lens) { Set result = Sets.newIdentityHashSet(); From cc976d6ab3dec863ed1e5417fd2980d92906d838 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 13 Jun 2023 14:26:37 +0200 Subject: [PATCH 106/153] Parallelize rewriting of AppInfo Change-Id: Ief3212edaa343d2069bf8ced618ea4c22a6e9d87 --- .../com/android/tools/r8/graph/AppView.java | 44 ++++++++++++------- .../tools/r8/utils/threads/ThreadTask.java | 2 + .../r8/utils/threads/ThreadTaskUtils.java | 5 +++ 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index e294798cfb..5a20e3d80c 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java @@ -963,29 +963,39 @@ private static void rewriteWithLensAndApplication( newMemberRebindingLens, () -> { GraphLens appliedLensInModifiedLens = GraphLens.getIdentityLens(); - if (appView.hasLiveness()) { - appView - .withLiveness() - .setAppInfo( - appView.appInfoWithLiveness().rewrittenWithLens(application, lens, timing)); - } else { - assert appView.hasClassHierarchy(); - AppView appViewWithClassHierarchy = - appView.withClassHierarchy(); - AppInfoWithClassHierarchy appInfo = appViewWithClassHierarchy.appInfo(); - MainDexInfo rewrittenMainDexInfo = - appInfo - .getMainDexInfo() - .rewrittenWithLens(appView.getSyntheticItems(), lens, timing); - appViewWithClassHierarchy.setAppInfo( - appInfo.rebuildWithMainDexInfo(rewrittenMainDexInfo)); - } ThreadTaskUtils.processTasks( executorService, appView.options(), timing .beginMerger("Rewrite AppView concurrently", executorService) .disableSlowestReporting(), + new ThreadTask() { + + private AppInfoWithClassHierarchy result; + + @Override + public void run(Timing timing) { + if (appView.hasLiveness()) { + result = + appView.appInfoWithLiveness().rewrittenWithLens(application, lens, timing); + } else { + assert appView.hasClassHierarchy(); + AppView appViewWithClassHierarchy = + appView.withClassHierarchy(); + AppInfoWithClassHierarchy appInfo = appViewWithClassHierarchy.appInfo(); + MainDexInfo rewrittenMainDexInfo = + appInfo + .getMainDexInfo() + .rewrittenWithLens(appView.getSyntheticItems(), lens, timing); + result = appInfo.rebuildWithMainDexInfo(rewrittenMainDexInfo); + } + } + + @Override + public void onJoin() { + appView.withClassHierarchy().setAppInfo(result); + } + }, new ThreadTask() { @Override public void run(Timing threadTiming) { diff --git a/src/main/java/com/android/tools/r8/utils/threads/ThreadTask.java b/src/main/java/com/android/tools/r8/utils/threads/ThreadTask.java index 84afbdc024..e8b89812e3 100644 --- a/src/main/java/com/android/tools/r8/utils/threads/ThreadTask.java +++ b/src/main/java/com/android/tools/r8/utils/threads/ThreadTask.java @@ -18,6 +18,8 @@ default void runWithRuntimeException(Timing timing) { } } + default void onJoin() {} + default boolean shouldRun() { return true; } diff --git a/src/main/java/com/android/tools/r8/utils/threads/ThreadTaskUtils.java b/src/main/java/com/android/tools/r8/utils/threads/ThreadTaskUtils.java index 63733bca2f..50dfe0333d 100644 --- a/src/main/java/com/android/tools/r8/utils/threads/ThreadTaskUtils.java +++ b/src/main/java/com/android/tools/r8/utils/threads/ThreadTaskUtils.java @@ -46,6 +46,11 @@ public static void processTasks( timingMerger.add(timings); timingMerger.end(); } + for (ThreadTask task : tasks) { + if (task.shouldRun()) { + task.onJoin(); + } + } } private static void processTask( From 63efc239ac701861433f7c9541e5752cc07c6981 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 13 Jun 2023 08:27:15 +0200 Subject: [PATCH 107/153] Improve pruning of art profiles Change-Id: Ifa7d4ac4c35fabb87214a75b65b0401f95f561c5 --- .../tools/r8/profile/art/ArtProfile.java | 17 ++--------------- .../art/NonEmptyArtProfileCollection.java | 5 ++--- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java index 784adbf1bd..3eee55aaa1 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java @@ -176,21 +176,8 @@ public ArtProfile withoutMissingItems(AppView appView) { } public ArtProfile withoutPrunedItems(PrunedItems prunedItems) { - return transform( - (classRule, classRuleBuilderFactory) -> { - if (!prunedItems.isRemoved(classRule.getType())) { - classRuleBuilderFactory.accept(classRule.getType()); - } - }, - (methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> { - if (!prunedItems.isRemoved(methodRule.getMethod())) { - methodRuleBuilderFactory - .apply(methodRule.getMethod()) - .acceptMethodRuleInfoBuilder( - methodRuleInfoBuilder -> - methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())); - } - }); + rules.keySet().removeIf(prunedItems::isRemoved); + return this; } private ArtProfile transform( diff --git a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java index 51132d8ebf..bcf3221c62 100644 --- a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java +++ b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java @@ -101,10 +101,9 @@ public ArtProfileCollection withoutMissingItems(AppView appView) { @Override public NonEmptyArtProfileCollection withoutPrunedItems(PrunedItems prunedItems, Timing timing) { timing.begin("Prune NonEmptyArtProfileCollection"); - NonEmptyArtProfileCollection result = - map(artProfile -> artProfile.withoutPrunedItems(prunedItems)); + forEach(artProfile -> artProfile.withoutPrunedItems(prunedItems)); timing.end(); - return result; + return this; } private NonEmptyArtProfileCollection map(Function fn) { From 8b9b7e6b9ce4c22cee5a616571ad18dc783c78fb Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Wed, 14 Jun 2023 20:27:48 +0200 Subject: [PATCH 108/153] Reimplement access modification as top-down class hierarchy traversal Bug: b/131130038 Bug: b/279124123 Bug: b/132677331 Bug: b/266345581 Change-Id: I0622ec061d19ec47232200c73edf757128089d87 --- src/main/java/com/android/tools/r8/R8.java | 10 +- ...odifier.java => LegacyAccessModifier.java} | 11 +- .../accessmodification/AccessModifier.java | 314 ++++++++++++++++++ .../AccessModifierLens.java | 117 +++++++ .../AccessModifierNamingState.java | 86 +++++ .../AccessModifierOptions.java | 29 ++ .../AccessModifierTraversal.java | 177 ++++++++++ .../tools/r8/utils/InternalOptions.java | 9 +- 8 files changed, 745 insertions(+), 8 deletions(-) rename src/main/java/com/android/tools/r8/optimize/{AccessModifier.java => LegacyAccessModifier.java} (95%) create mode 100644 src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java create mode 100644 src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java create mode 100644 src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierNamingState.java create mode 100644 src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java create mode 100644 src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 7bd87671f6..1684062512 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -62,10 +62,11 @@ import com.android.tools.r8.naming.ProguardMapMinifier; import com.android.tools.r8.naming.RecordRewritingNamingLens; import com.android.tools.r8.naming.signature.GenericSignatureRewriter; -import com.android.tools.r8.optimize.AccessModifier; +import com.android.tools.r8.optimize.LegacyAccessModifier; import com.android.tools.r8.optimize.MemberRebindingAnalysis; import com.android.tools.r8.optimize.MemberRebindingIdentityLens; import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory; +import com.android.tools.r8.optimize.accessmodification.AccessModifier; import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting; import com.android.tools.r8.optimize.fields.FieldFinalizer; import com.android.tools.r8.optimize.interfaces.analysis.CfOpenClosedInterfacesAnalysis; @@ -448,7 +449,8 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO // to clear the cache, so that we will recompute the type lattice elements. appView.dexItemFactory().clearTypeElementsCache(); - AccessModifier.run(appViewWithLiveness, executorService, timing); + // TODO(b/132677331): Remove legacy access modifier. + LegacyAccessModifier.run(appViewWithLiveness, executorService, timing); if (appView.graphLens().isPublicizerLens()) { // We can now remove redundant bridges. Note that we do not need to update the // invoke-targets here, as the existing invokes will simply dispatch to the now @@ -463,6 +465,10 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO new MemberRebindingAnalysis(appViewWithLiveness).run(executorService); appViewWithLiveness.appInfo().notifyMemberRebindingFinished(appViewWithLiveness); + assert ArtProfileCompletenessChecker.verify(appView); + + AccessModifier.run(appViewWithLiveness, executorService, timing); + boolean isKotlinLibraryCompilationWithInlinePassThrough = options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods(); diff --git a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java similarity index 95% rename from src/main/java/com/android/tools/r8/optimize/AccessModifier.java rename to src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java index dea634656f..2c82527092 100644 --- a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java +++ b/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java @@ -24,6 +24,7 @@ import com.android.tools.r8.ir.optimize.MethodPoolCollection; import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.MethodSignatureEquivalence; import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.Timing; @@ -33,7 +34,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -public final class AccessModifier { +public final class LegacyAccessModifier { private final AppView appView; private final SubtypingInfo subtypingInfo; @@ -41,7 +42,7 @@ public final class AccessModifier { private final PublicizedLensBuilder lensBuilder = PublicizerLens.createBuilder(); - private AccessModifier(AppView appView) { + private LegacyAccessModifier(AppView appView) { this.appView = appView; this.subtypingInfo = appView.appInfo().computeSubtypingInfo(); this.methodPoolCollection = @@ -59,9 +60,11 @@ private AccessModifier(AppView appView) { public static void run( AppView appView, ExecutorService executorService, Timing timing) throws ExecutionException { - if (appView.options().getProguardConfiguration().isAccessModificationAllowed()) { + InternalOptions options = appView.options(); + if (options.isAccessModificationEnabled() + && !options.getAccessModifierOptions().isExperimentalAccessModificationEnabled()) { timing.begin("Access modification"); - new AccessModifier(appView).internalRun(executorService, timing); + new LegacyAccessModifier(appView).internalRun(executorService, timing); timing.end(); } } diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java new file mode 100644 index 0000000000..b7342dcb2d --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java @@ -0,0 +1,314 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import static com.android.tools.r8.dex.Constants.ACC_PRIVATE; +import static com.android.tools.r8.dex.Constants.ACC_PROTECTED; +import static com.android.tools.r8.dex.Constants.ACC_PUBLIC; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexMethodSignature; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.FieldAccessFlags; +import com.android.tools.r8.graph.FieldAccessInfo; +import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; +import com.android.tools.r8.graph.InnerClassAttribute; +import com.android.tools.r8.graph.MethodAccessFlags; +import com.android.tools.r8.graph.ProgramDefinition; +import com.android.tools.r8.graph.ProgramField; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.optimize.accessmodification.AccessModifierTraversal.BottomUpTraversalState; +import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph; +import com.android.tools.r8.optimize.utils.ConcurrentNonProgramMethodsCollection; +import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.KeepMethodInfo; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.ListUtils; +import com.android.tools.r8.utils.OptionalBool; +import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +public class AccessModifier { + + private final AppView appView; + private final ImmediateProgramSubtypingInfo immediateSubtypingInfo; + private final AccessModifierLens.Builder lensBuilder = AccessModifierLens.builder(); + private final NonProgramMethodsCollection nonProgramMethodsCollection; + private final InternalOptions options; + + private AccessModifier(AppView appView) { + this.appView = appView; + this.immediateSubtypingInfo = + ImmediateProgramSubtypingInfo.createWithDeterministicOrder(appView); + this.nonProgramMethodsCollection = + ConcurrentNonProgramMethodsCollection.createVirtualMethodsCollection(appView); + this.options = appView.options(); + } + + public static void run( + AppView appView, ExecutorService executorService, Timing timing) + throws ExecutionException { + timing.begin("Access modification"); + if (appView.options().getAccessModifierOptions().isExperimentalAccessModificationEnabled()) { + new AccessModifier(appView) + .processStronglyConnectedComponents(executorService) + .installLens(executorService, timing); + } + timing.end(); + } + + private AccessModifier processStronglyConnectedComponents(ExecutorService executorService) + throws ExecutionException { + // Compute the connected program classes and process the components in parallel. + List> stronglyConnectedComponents = + new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo) + .computeStronglyConnectedComponents(); + ThreadUtils.processItems( + stronglyConnectedComponents, this::processStronglyConnectedComponent, executorService); + return this; + } + + private void processStronglyConnectedComponent(Set stronglyConnectedComponent) { + // Perform a top-down traversal over the class hierarchy. + new AccessModifierTraversal( + appView, + immediateSubtypingInfo, + this, + AccessModifierNamingState.createInitialNamingState( + appView, stronglyConnectedComponent, nonProgramMethodsCollection)) + .run(ListUtils.sort(stronglyConnectedComponent, Comparator.comparing(DexClass::getType))); + } + + private void installLens(ExecutorService executorService, Timing timing) + throws ExecutionException { + if (!lensBuilder.isEmpty()) { + appView.rewriteWithLens(lensBuilder.build(appView), executorService, timing); + } + } + + // Publicizing of classes and members. + + void processClass( + DexProgramClass clazz, + AccessModifierNamingState namingState, + BottomUpTraversalState traversalState) { + publicizeClass(clazz); + publicizeFields(clazz); + publicizeMethods(clazz, namingState, traversalState); + // TODO(b/278736230): Also finalize classes and methods here. + finalizeFields(clazz); + } + + private void publicizeClass(DexProgramClass clazz) { + if (isAccessModificationAllowed(clazz) && !clazz.getAccessFlags().isPublic()) { + clazz.getAccessFlags().promoteToPublic(); + } + + // Update inner class attribute. + // TODO(b/285494837): Carry-over from the legacy access modifier. We should never publicize + // items unconditionally, but account for keep info. + InnerClassAttribute attr = clazz.getInnerClassAttributeForThisClass(); + if (attr != null) { + int accessFlags = ((attr.getAccess() | ACC_PUBLIC) & ~ACC_PRIVATE) & ~ACC_PROTECTED; + clazz.replaceInnerClassAttributeForThisClass( + new InnerClassAttribute( + accessFlags, attr.getInner(), attr.getOuter(), attr.getInnerName())); + } + } + + private void publicizeFields(DexProgramClass clazz) { + clazz.forEachProgramField(this::publicizeField); + } + + private void publicizeField(ProgramField field) { + if (isAccessModificationAllowed(field) && !field.getAccessFlags().isPublic()) { + field.getAccessFlags().promoteToPublic(); + } + } + + private void publicizeMethods( + DexProgramClass clazz, + AccessModifierNamingState namingState, + BottomUpTraversalState traversalState) { + // Create a local naming state to keep track of the methods present on the current class. + // Start by reserving the pinned method signatures on the current class. + BiMap localNamingState = HashBiMap.create(); + clazz.forEachProgramMethod( + method -> { + if (!method.getDefinition().isInitializer() && !isRenamingAllowed(method)) { + localNamingState.put(method.getReference(), method.getReference()); + } + }); + clazz + .getMethodCollection() + .replaceClassAndMethods( + method -> publicizeMethod(method, localNamingState, namingState, traversalState)); + } + + private DexEncodedMethod publicizeMethod( + ProgramMethod method, + BiMap localNamingState, + AccessModifierNamingState namingState, + BottomUpTraversalState traversalState) { + MethodAccessFlags accessFlags = method.getAccessFlags(); + if (accessFlags.isPublic() || !isAccessModificationAllowed(method)) { + return commitMethod(method, localNamingState, namingState); + } + + if (method.getDefinition().isInstanceInitializer() + || (accessFlags.isPackagePrivate() + && !traversalState.hasIllegalOverrideOfPackagePrivateMethod(method)) + || accessFlags.isProtected()) { + method.getAccessFlags().promoteToPublic(); + return commitMethod(method, localNamingState, namingState); + } + + if (accessFlags.isPrivate()) { + if (isRenamingAllowed(method)) { + method.getAccessFlags().promoteToPublic(); + return commitMethod(method, localNamingState, namingState); + } + assert localNamingState.containsKey(method.getReference()); + assert localNamingState.get(method.getReference()) == method.getReference(); + if (namingState.isFree(method.getMethodSignature())) { + method.getAccessFlags().promoteToPublic(); + namingState.addBlockedMethodSignature(method.getMethodSignature()); + } + return commitMethod(method, method.getReference()); + } + + // TODO(b/279126633): Add support for publicizing package-private methods by renaming. + assert accessFlags.isPackagePrivate(); + assert traversalState.hasIllegalOverrideOfPackagePrivateMethod(method); + return commitMethod(method, localNamingState, namingState); + } + + private DexMethod getAndReserveNewMethodReference( + ProgramMethod method, + BiMap localNamingState, + AccessModifierNamingState namingState) { + if (method.getDefinition().isInitializer()) { + return method.getReference(); + } + if (!isRenamingAllowed(method)) { + assert localNamingState.containsKey(method.getReference()); + assert localNamingState.get(method.getReference()) == method.getReference(); + assert method.getAccessFlags().isPrivate() + || method + .getMethodSignature() + .equals(namingState.getReservedSignature(method.getMethodSignature())); + return method.getReference(); + } + DexItemFactory dexItemFactory = appView.dexItemFactory(); + if (method.getAccessFlags().isPrivate()) { + // Find a fresh method name and reserve it for the current class. + DexMethod newMethodReference = + dexItemFactory.createFreshMethodNameWithoutHolder( + method.getName().toString(), + method.getProto(), + method.getHolderType(), + candidate -> + !localNamingState.containsValue(candidate) + && namingState.isFree(candidate.getSignature())); + localNamingState.put(method.getReference(), newMethodReference); + return newMethodReference; + } + // Check if a mapping already exists for this method signature. + if (!method.getAccessFlags().isPromotedFromPrivateToPublic()) { + DexMethodSignature reservedSignature = + namingState.getReservedSignature(method.getMethodSignature()); + if (reservedSignature != null) { + return reservedSignature.withHolder(method, appView.dexItemFactory()); + } + } + // Find a fresh method name and block/reserve it globally. + DexMethod newMethodReference = + dexItemFactory.createFreshMethodNameWithoutHolder( + method.getName().toString(), + method.getProto(), + method.getHolderType(), + candidate -> + !localNamingState.containsValue(candidate) + && namingState.isFree(candidate.getSignature())); + if (method.getAccessFlags().belongsToVirtualPool()) { + if (method.getAccessFlags().isPromotedFromPrivateToPublic()) { + namingState.addBlockedMethodSignature(newMethodReference.getSignature()); + } else { + namingState.addRenaming(method.getMethodSignature(), newMethodReference.getSignature()); + } + } + return newMethodReference; + } + + private boolean isAccessModificationAllowed(ProgramDefinition definition) { + // TODO(b/278687711): Also check that the definition does not have any illegal accesses to it. + return appView.getKeepInfo(definition).isAccessModificationAllowed(options); + } + + private boolean isRenamingAllowed(ProgramMethod method) { + KeepMethodInfo keepInfo = appView.getKeepInfo(method); + return keepInfo.isOptimizationAllowed(options) && keepInfo.isShrinkingAllowed(options); + } + + private DexEncodedMethod commitMethod( + ProgramMethod method, + BiMap localNamingState, + AccessModifierNamingState namingState) { + return commitMethod( + method, getAndReserveNewMethodReference(method, localNamingState, namingState)); + } + + private DexEncodedMethod commitMethod(ProgramMethod method, DexMethod newMethodReference) { + DexProgramClass holder = method.getHolder(); + if (newMethodReference != method.getReference()) { + lensBuilder.recordMove(method.getReference(), newMethodReference); + method = + new ProgramMethod( + holder, method.getDefinition().toTypeSubstitutedMethod(newMethodReference)); + } + if (method.getAccessFlags().isPromotedFromPrivateToPublic() + && method.getAccessFlags().belongsToVirtualPool()) { + lensBuilder.addPublicizedPrivateVirtualMethod(method.getHolder(), newMethodReference); + method.getDefinition().setLibraryMethodOverride(OptionalBool.FALSE); + } + return method.getDefinition(); + } + + // Finalization of classes and members. + + private void finalizeFields(DexProgramClass clazz) { + clazz.forEachProgramField(this::finalizeField); + } + + private void finalizeField(ProgramField field) { + FieldAccessFlags flags = field.getAccessFlags(); + FieldAccessInfo accessInfo = + appView.appInfo().getFieldAccessInfoCollection().get(field.getReference()); + if (!appView.getKeepInfo(field).isPinned(options) + && !accessInfo.hasReflectiveWrite() + && !accessInfo.isWrittenFromMethodHandle() + && accessInfo.isWrittenOnlyInMethodSatisfying( + method -> + method.getDefinition().isInitializer() + && method.getAccessFlags().isStatic() == flags.isStatic() + && method.getHolder() == field.getHolder()) + && !flags.isFinal() + && !flags.isVolatile()) { + flags.promoteToFinal(); + } + } +} diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java new file mode 100644 index 0000000000..76da486d84 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java @@ -0,0 +1,117 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.lens.DefaultNonIdentityGraphLens; +import com.android.tools.r8.graph.lens.GraphLens; +import com.android.tools.r8.graph.lens.MethodLookupResult; +import com.android.tools.r8.ir.code.InvokeType; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; +import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap; +import com.google.common.collect.Sets; +import java.util.Set; + +public class AccessModifierLens extends DefaultNonIdentityGraphLens { + + private final BidirectionalOneToOneMap methodMap; + + // Private interface methods that have been publicized. Invokes targeting these methods must be + // rewritten from invoke-direct to invoke-interface. + private final Set publicizedPrivateInterfaceMethods; + + // Private class methods that have been publicized. Invokes targeting these methods must be + // rewritten from invoke-direct to invoke-virtual. + private final Set publicizedPrivateVirtualMethods; + + AccessModifierLens( + AppView appView, + BidirectionalOneToOneMap methodMap, + Set publicizedPrivateInterfaceMethods, + Set publicizedPrivateVirtualMethods) { + super(appView); + this.methodMap = methodMap; + this.publicizedPrivateInterfaceMethods = publicizedPrivateInterfaceMethods; + this.publicizedPrivateVirtualMethods = publicizedPrivateVirtualMethods; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public DexMethod getNextMethodSignature(DexMethod method) { + return methodMap.getOrDefault(method, method); + } + + @Override + public DexMethod getPreviousMethodSignature(DexMethod method) { + return methodMap.getRepresentativeKeyOrDefault(method, method); + } + + @Override + public MethodLookupResult internalDescribeLookupMethod( + MethodLookupResult previous, DexMethod context, GraphLens codeLens) { + assert !previous.hasReboundReference(); + DexMethod newMethod = getNextMethodSignature(previous.getReference()); + InvokeType newInvokeType = previous.getType(); + if (previous.getType() == InvokeType.DIRECT) { + if (publicizedPrivateInterfaceMethods.contains(newMethod)) { + newInvokeType = InvokeType.INTERFACE; + } else if (publicizedPrivateVirtualMethods.contains(newMethod)) { + newInvokeType = InvokeType.VIRTUAL; + } + } + if (newInvokeType != previous.getType() || newMethod != previous.getReference()) { + return MethodLookupResult.builder(this) + .setReference(newMethod) + .setPrototypeChanges(previous.getPrototypeChanges()) + .setType(newInvokeType) + .build(); + } + return previous; + } + + public static class Builder { + + private final MutableBidirectionalOneToOneMap methodMap = + new BidirectionalOneToOneHashMap<>(); + private final Set publicizedPrivateInterfaceMethods = Sets.newConcurrentHashSet(); + private final Set publicizedPrivateVirtualMethods = Sets.newConcurrentHashSet(); + + public Builder addPublicizedPrivateVirtualMethod(DexProgramClass holder, DexMethod method) { + if (holder.isInterface()) { + publicizedPrivateInterfaceMethods.add(method); + } else { + publicizedPrivateVirtualMethods.add(method); + } + return this; + } + + public Builder recordMove(DexMethod from, DexMethod to) { + assert from != to; + synchronized (methodMap) { + methodMap.put(from, to); + } + return this; + } + + public boolean isEmpty() { + return methodMap.isEmpty() + && publicizedPrivateInterfaceMethods.isEmpty() + && publicizedPrivateVirtualMethods.isEmpty(); + } + + public AccessModifierLens build(AppView appView) { + assert !isEmpty(); + return new AccessModifierLens( + appView, methodMap, publicizedPrivateInterfaceMethods, publicizedPrivateVirtualMethods); + } + } +} diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierNamingState.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierNamingState.java new file mode 100644 index 0000000000..cae79be5e6 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierNamingState.java @@ -0,0 +1,86 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ClasspathOrLibraryClass; +import com.android.tools.r8.graph.DexMethodSignature; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.KeepMethodInfo; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap; +import com.android.tools.r8.utils.collections.DexMethodSignatureSet; +import com.google.common.collect.Sets; +import java.util.Set; + +public class AccessModifierNamingState { + + // The set of private method signatures that have been publicized. These method signatures are + // "blocked" to ensure that virtual methods with the same method signature are given a different + // name. + private final DexMethodSignatureSet blockedMethodSignatures = DexMethodSignatureSet.create(); + + // Records which method signatures in the component have been mapped to. This uses a bidirectional + // map to allow efficiently finding a fresh method signature in the component. + private final DexMethodSignatureBiMap reservedMethodSignatures; + + private AccessModifierNamingState( + DexMethodSignatureBiMap reservedMethodSignatures) { + this.reservedMethodSignatures = reservedMethodSignatures; + } + + static AccessModifierNamingState createInitialNamingState( + AppView appView, + Set stronglyConnectedComponent, + NonProgramMethodsCollection nonProgramMethodsCollection) { + DexMethodSignatureBiMap reservedSignatures = + new DexMethodSignatureBiMap<>(); + Set seenNonProgramClasses = Sets.newIdentityHashSet(); + for (DexProgramClass clazz : stronglyConnectedComponent) { + // Reserve the signatures that are pinned in this class. + clazz.forEachProgramMethodMatching( + method -> !method.isInstanceInitializer() && !method.getAccessFlags().isPrivate(), + method -> { + KeepMethodInfo keepInfo = appView.getKeepInfo(method); + InternalOptions options = appView.options(); + if (!keepInfo.isOptimizationAllowed(options) || !keepInfo.isShrinkingAllowed(options)) { + DexMethodSignature methodSignature = method.getMethodSignature(); + reservedSignatures.put(methodSignature, methodSignature); + } + }); + // Reserve the signatures in the library. + clazz.forEachImmediateSuperClassMatching( + appView, + (supertype, superclass) -> + superclass != null + && !superclass.isProgramClass() + && seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass()), + (supertype, superclass) -> + reservedSignatures.putAllToIdentity( + nonProgramMethodsCollection.getOrComputeNonProgramMethods( + superclass.asClasspathOrLibraryClass()))); + } + return new AccessModifierNamingState(reservedSignatures); + } + + void addBlockedMethodSignature(DexMethodSignature signature) { + blockedMethodSignatures.add(signature); + } + + void addRenaming(DexMethodSignature signature, DexMethodSignature newSignature) { + reservedMethodSignatures.put(signature, newSignature); + } + + DexMethodSignature getReservedSignature(DexMethodSignature signature) { + return reservedMethodSignatures.get(signature); + } + + boolean isFree(DexMethodSignature signature) { + return !blockedMethodSignatures.contains(signature) + && !reservedMethodSignatures.containsValue(signature); + } +} diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java new file mode 100644 index 0000000000..7ee64c7ca1 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java @@ -0,0 +1,29 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import com.android.tools.r8.utils.InternalOptions; + +public class AccessModifierOptions { + + // TODO(b/132677331): Enable new access modifier by default. + private boolean enableExperimentalAccessModification = false; + + private InternalOptions options; + + public AccessModifierOptions(InternalOptions options) { + this.options = options; + } + + public boolean isAccessModificationEnabled() { + return options.hasProguardConfiguration() + && options.getProguardConfiguration().isAccessModificationAllowed(); + } + + public boolean isExperimentalAccessModificationEnabled() { + // TODO(b/132677331): Do not require -allowaccessmodification in R8 full mode. + return isAccessModificationEnabled() && enableExperimentalAccessModification; + } +} diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java new file mode 100644 index 0000000000..1fef67fd06 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java @@ -0,0 +1,177 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; +import static com.android.tools.r8.utils.MapUtils.ignoreKey; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.MapUtils; +import com.android.tools.r8.utils.collections.DexMethodSignatureMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +class AccessModifierTraversal extends DepthFirstTopDownClassHierarchyTraversal { + + private final AccessModifier accessModifier; + private final AccessModifierNamingState namingState; + + private final Map states = new IdentityHashMap<>(); + + AccessModifierTraversal( + AppView appView, + ImmediateProgramSubtypingInfo immediateSubtypingInfo, + AccessModifier accessModifier, + AccessModifierNamingState namingState) { + super(appView, immediateSubtypingInfo); + this.accessModifier = accessModifier; + this.namingState = namingState; + } + + /** Predicate that specifies which program classes the depth-first traversal should start from. */ + @Override + public boolean isRoot(DexProgramClass clazz) { + return Iterables.all( + clazz.allImmediateSupertypes(), + supertype -> asProgramClassOrNull(appView.definitionFor(supertype)) == null); + } + + /** Called when {@param clazz} is visited for the first time during the downwards traversal. */ + @Override + public void visit(DexProgramClass clazz) { + // TODO(b/279126633): Store a top down traversal state for the current class, which contains the + // protected and public method signatures when traversing downwards to enable publicizing of + // package private methods with illegal overrides. + states.put(clazz.getType(), TopDownTraversalState.empty()); + } + + /** Called during backtracking when all subclasses of {@param clazz} have been processed. */ + @Override + public void prune(DexProgramClass clazz) { + // Remove the traversal state since all subclasses have now been processed. + states.remove(clazz.getType()); + + // Remove and join the bottom up traversal states of the subclasses. + BottomUpTraversalState state = new BottomUpTraversalState(); + forEachSubClass( + clazz, + subclass -> { + BottomUpTraversalState subState = + MapUtils.removeOrDefault(states, subclass.getType(), BottomUpTraversalState.empty()) + .asBottomUpTraversalState(); + state.add(subState); + }); + + // Apply access modification to the class and its members. + accessModifier.processClass(clazz, namingState, state); + + // Add the methods of the current class. + clazz.forEachProgramVirtualMethod(state::addMethod); + + // Store the bottom up traversal state for the current class. + if (state.isEmpty()) { + states.remove(clazz.getType()); + } else { + states.put(clazz.getType(), state); + } + } + + abstract static class TraversalState { + + BottomUpTraversalState asBottomUpTraversalState() { + return null; + } + + TopDownTraversalState asTopDownTraversalState() { + return null; + } + } + + // TODO(b/279126633): Collect the protected and public method signatures when traversing downwards + // to enable publicizing of package private methods with illegal overrides. + static class TopDownTraversalState extends TraversalState { + + private static final TopDownTraversalState EMPTY = new TopDownTraversalState(); + + static TopDownTraversalState empty() { + return EMPTY; + } + + @Override + TopDownTraversalState asTopDownTraversalState() { + return this; + } + + boolean isEmpty() { + return true; + } + } + + static class BottomUpTraversalState extends TraversalState { + + private static final BottomUpTraversalState EMPTY = + new BottomUpTraversalState(DexMethodSignatureMap.empty()); + + // The set of non-private virtual methods below the current class. + DexMethodSignatureMap> nonPrivateVirtualMethods; + + BottomUpTraversalState() { + this(DexMethodSignatureMap.create()); + } + + BottomUpTraversalState(DexMethodSignatureMap> packagePrivateMethods) { + this.nonPrivateVirtualMethods = packagePrivateMethods; + } + + static BottomUpTraversalState empty() { + return EMPTY; + } + + @Override + BottomUpTraversalState asBottomUpTraversalState() { + return this; + } + + void add(BottomUpTraversalState backtrackingState) { + backtrackingState.nonPrivateVirtualMethods.forEach( + (methodSignature, packageDescriptors) -> + this.nonPrivateVirtualMethods + .computeIfAbsent(methodSignature, ignoreKey(HashSet::new)) + .addAll(packageDescriptors)); + } + + void addMethod(ProgramMethod method) { + assert method.getDefinition().belongsToVirtualPool(); + nonPrivateVirtualMethods + .computeIfAbsent(method.getMethodSignature(), ignoreKey(Sets::newIdentityHashSet)) + .add(method.getHolderType().getPackageDescriptor()); + } + + boolean hasIllegalOverrideOfPackagePrivateMethod(ProgramMethod method) { + assert method.getAccessFlags().isPackagePrivate(); + String methodPackageDescriptor = method.getHolderType().getPackageDescriptor(); + return Iterables.any( + nonPrivateVirtualMethods.getOrDefault( + method.getMethodSignature(), Collections.emptySet()), + methodOverridePackageDescriptor -> + !methodOverridePackageDescriptor.equals(methodPackageDescriptor)); + } + + boolean isEmpty() { + return nonPrivateVirtualMethods.isEmpty(); + } + } +} 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 538b9ee49c..6259f3a7b2 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -74,6 +74,7 @@ import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.naming.MapConsumer; import com.android.tools.r8.naming.MapVersion; +import com.android.tools.r8.optimize.accessmodification.AccessModifierOptions; import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer; import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalOptions; import com.android.tools.r8.origin.Origin; @@ -843,8 +844,7 @@ public boolean isClassMergingExtensionRequired(Enqueuer.Mode mode) { @Override public boolean isAccessModificationEnabled() { - return getProguardConfiguration() != null - && getProguardConfiguration().isAccessModificationAllowed(); + return accessModifierOptions.isAccessModificationEnabled(); } @Override @@ -879,6 +879,7 @@ public boolean canUseInputStackMaps() { public boolean debug = false; + private final AccessModifierOptions accessModifierOptions = new AccessModifierOptions(this); private final RewriteArrayOptions rewriteArrayOptions = new RewriteArrayOptions(); private final CallSiteOptimizationOptions callSiteOptimizationOptions = new CallSiteOptimizationOptions(); @@ -956,6 +957,10 @@ public DesugarSpecificOptions desugarSpecificOptions() { return desugarSpecificOptions; } + public AccessModifierOptions getAccessModifierOptions() { + return accessModifierOptions; + } + public CfCodeAnalysisOptions getCfCodeAnalysisOptions() { return cfCodeAnalysisOptions; } From cf43a41edaf8a3fd5ae68bc741903d2fa3049738 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Wed, 14 Jun 2023 20:27:57 +0200 Subject: [PATCH 109/153] Misc. tests for access modification Change-Id: I4e16e4d3dc64111fec09ef0394f7351c5a64bfe2 --- ...blingConsistentRenamingPublicizerTest.java | 129 ++++++++++++++++++ .../PrivateOverrideOfPublicMethodTest.java | 123 +++++++++++++++++ .../PrivateShadowOfPrivateMethodTest.java | 104 ++++++++++++++ .../PublicOverrideOfPrivateMethodTest.java | 91 ++++++++++++ 4 files changed, 447 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/InterfaceMethodAndSiblingConsistentRenamingPublicizerTest.java create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PublicOverrideOfPrivateMethodTest.java diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InterfaceMethodAndSiblingConsistentRenamingPublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InterfaceMethodAndSiblingConsistentRenamingPublicizerTest.java new file mode 100644 index 0000000000..0cf8434f3a --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/InterfaceMethodAndSiblingConsistentRenamingPublicizerTest.java @@ -0,0 +1,129 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InterfaceMethodAndSiblingConsistentRenamingPublicizerTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + // Check that B.foo(), C.foo() and I.foo() are all present in the output. + ClassSubject bClassSubject = inspector.clazz(B.class); + assertThat(bClassSubject, isPresent()); + + MethodSubject bMethodSubject = bClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(bMethodSubject, isPresent()); + + ClassSubject cClassSubject = inspector.clazz(C.class); + assertThat(cClassSubject, isPresent()); + + MethodSubject cMethodSubject = cClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(cMethodSubject, isPresent()); + + ClassSubject iClassSubject = inspector.clazz(I.class); + assertThat(iClassSubject, isPresent()); + + MethodSubject iMethodSubject = iClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(iMethodSubject, isPresent()); + + // Check that B.foo() is renamed to one name, and C.foo() and I.foo() is renamed to + // another name. The reason for B.foo() being given another name is that we use a + // single naming state for the classes, which means that we must give B.foo() another + // name to ensure we do not introduce new method overriding relationships from + // publicizing. + assertNotEquals(bMethodSubject.getFinalName(), cMethodSubject.getFinalName()); + assertEquals(cMethodSubject.getFinalName(), iMethodSubject.getFinalName()); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("B", "C", "C"); + } + + static class Main { + + public static void main(String[] args) { + new B().foo(); + new C().foo(); + I i = System.currentTimeMillis() > 0 ? new D() : new E(); + i.foo(); + } + } + + interface I { + + void foo(); + } + + static class A {} + + @NeverClassInline + @NoHorizontalClassMerging + static class B extends A { + + @NeverInline + private void foo() { + System.out.println("B"); + } + } + + @NeverClassInline + @NoHorizontalClassMerging + static class C extends A { + + // Implements I.foo(). + @NeverInline + public void foo() { + System.out.println("C"); + } + } + + @NoHorizontalClassMerging + static class D extends C implements I {} + + @NoHorizontalClassMerging + static class E extends C implements I { + + @Override + public void foo() { + System.out.println("E"); + } + } +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java new file mode 100644 index 0000000000..ea6a6a0680 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java @@ -0,0 +1,123 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import static com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate.onName; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.utils.StringUtils; +import java.io.IOException; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PrivateOverrideOfPublicMethodTest extends TestBase { + + private static final String EXPECTED_OUTPUT = StringUtils.lines("A", "B", "IAE", "A", "B", "A"); + private static final String EXPECTED_OUTPUT_5_TO_6 = + StringUtils.lines("A", "A", "A", "A", "A", "A"); + private static final String EXPECTED_OUTPUT_7 = StringUtils.lines("A", "B", "A", "A", "B", "A"); + + private static byte[] programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws IOException { + programClassFileData = + transformer(B.class) + .renameMethod(onName("bar"), "foo") + .transformMethodInsnInMethod( + "", + (opcode, owner, name, descriptor, isInterface, continuation) -> + continuation.visitMethodInsn( + opcode, owner, name.equals("bar") ? "foo" : name, descriptor, isInterface)) + .transform(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class, A.class) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .applyIf( + parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V5_1_1) + && parameters.isDexRuntimeVersionOlderThanOrEqual(Version.V6_0_1), + runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT_5_TO_6), + parameters.isDexRuntimeVersion(Version.V7_0_0), + runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT_7), + runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class, A.class) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .allowAccessModification() + .enableInliningAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + static class Main { + + public static void main(String[] args) { + new A().foo(); + try { + new B().foo(); + } catch (IllegalAccessError e) { + System.out.println("IAE"); + } + A a = System.currentTimeMillis() > 0 ? new A() : new B(); + a.foo(); + A b = System.currentTimeMillis() > 0 ? new B() : new A(); + b.foo(); + } + } + + @NoVerticalClassMerging + static class A { + + @NeverInline + public void foo() { + System.out.println("A"); + } + } + + static class B extends A { + + B() { + if (System.currentTimeMillis() > 0) { + bar(); + } + } + + // Renamed to foo. + @NeverInline + private void bar() { + System.out.println("B"); + } + } +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java new file mode 100644 index 0000000000..4698b94805 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java @@ -0,0 +1,104 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPrivate; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PrivateShadowOfPrivateMethodTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .allowAccessModification() + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + // Check that A.foo() is publicized. + ClassSubject aClassSubject = inspector.clazz(A.class); + assertThat(aClassSubject, isPresent()); + + MethodSubject aMethodSubject = aClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(aMethodSubject, isPresent()); + assertThat(aMethodSubject, isPublic()); + + // Check that B.foo is still private. + ClassSubject bClassSubject = inspector.clazz(B.class); + assertThat(bClassSubject, isPresent()); + + MethodSubject bMethodSubject = bClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(bMethodSubject, isPresent()); + assertThat(bMethodSubject, isPrivate()); + + // Verify that the two methods still have the same name. + assertEquals(aMethodSubject.getFinalName(), bMethodSubject.getFinalName()); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A", "B", "A", "A"); + } + + static class Main { + + public static void main(String[] args) { + new A().foo(); + new B().foo(); + A a = System.currentTimeMillis() > 0 ? new A() : new B(); + a.foo(); + A b = System.currentTimeMillis() > 0 ? new B() : new A(); + b.foo(); + } + } + + @NeverClassInline + @NoVerticalClassMerging + static class A { + + @NeverInline + private void foo() { + System.out.println("A"); + } + } + + @NeverClassInline + static class B extends A { + + @NeverInline + private void foo() { + System.out.println("B"); + } + } +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PublicOverrideOfPrivateMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PublicOverrideOfPrivateMethodTest.java new file mode 100644 index 0000000000..09d345628a --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PublicOverrideOfPrivateMethodTest.java @@ -0,0 +1,91 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PublicOverrideOfPrivateMethodTest extends TestBase { + + private static final String EXPECTED_OUTPUT = StringUtils.lines("A", "B", "B", "A", "B", "A"); + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addInnerClasses(getClass()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .allowAccessModification() + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + static class Main { + + public static void main(String[] args) { + new A().foo(); + new B().foo(); + A a = System.currentTimeMillis() > 0 ? new A() : new B(); + a.foo(); + A b = System.currentTimeMillis() > 0 ? new B() : new A(); + b.foo(); + } + } + + @NeverClassInline + @NoVerticalClassMerging + static class A { + + @NeverInline + private void foo() { + System.out.println("A"); + } + } + + @NeverClassInline + static class B extends A { + + B() { + if (System.currentTimeMillis() > 0) { + foo(); + } + } + + @NeverInline + public void foo() { + System.out.println("B"); + } + } +} From 001898218defb85a2b29a7f9275872b32546f2ac Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Wed, 14 Jun 2023 20:28:08 +0200 Subject: [PATCH 110/153] Enable new access modifier Change-Id: I15236cd1c29fec14625332c7f9826645f739d579 --- .../AccessModifierOptions.java | 3 +- .../EffectiveFinalFieldMarkedFinalTest.java | 7 +- .../InnerClassAttributePublicizerTest.java | 2 + .../NoRelaxationForSerializableTest.java | 106 ++++++++-------- .../NonConstructorRelaxationTest.java | 119 +++++++++--------- .../PrivateOverrideOfPublicMethodTest.java | 4 +- .../PrivateShadowOfPrivateMethodTest.java | 11 +- .../privateinstance/Base.java | 2 + .../tools/r8/naming/b72391662/B72391662.java | 26 ++-- .../ifrule/IfOnAccessModifierTest.java | 6 +- 10 files changed, 143 insertions(+), 143 deletions(-) diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java index 7ee64c7ca1..e944d38e50 100644 --- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java @@ -8,8 +8,7 @@ public class AccessModifierOptions { - // TODO(b/132677331): Enable new access modifier by default. - private boolean enableExperimentalAccessModification = false; + private boolean enableExperimentalAccessModification = true; private InternalOptions options; diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java index e08fd3e36c..d491189359 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java @@ -52,12 +52,7 @@ public void test() throws Exception { assertThat(mainClassSubject, isPresent()); assertThat( mainClassSubject.uniqueFieldWithOriginalName("instanceField"), - allOf( - isPresent(), - onlyIf( - allowAccessModification - && !parameters.canInitNewInstanceUsingSuperclassConstructor(), - isFinal()))); + allOf(isPresent(), onlyIf(allowAccessModification, isFinal()))); assertThat( mainClassSubject.uniqueFieldWithOriginalName("staticField"), allOf(isPresent(), onlyIf(allowAccessModification, isFinal()))); diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java index 41c73cf174..8c3c7c32cf 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java @@ -5,6 +5,7 @@ package com.android.tools.r8.accessrelaxation; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -52,6 +53,7 @@ public void test() throws Exception { private void inspect(CodeInspector inspector) { ClassSubject classSubject = inspector.clazz(Outer.Inner.class); assertThat(classSubject, isPresent()); + assertThat(classSubject, isPublic()); InnerClassAttribute innerClassAttribute = classSubject.getDexProgramClass().getInnerClassAttributeForThisClass(); diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java index 5c85d69814..7fcbf6d0a3 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java @@ -10,12 +10,12 @@ import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverPropagateValue; import com.android.tools.r8.NoVerticalClassMerging; -import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.ProguardVersion; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.google.common.collect.ImmutableList; @@ -25,12 +25,12 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; import java.util.List; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; @NoVerticalClassMerging class MySerializable implements Serializable { @@ -99,9 +99,8 @@ public class NoRelaxationForSerializableTest extends AccessRelaxationTestBase { ); private final boolean accessModification; - private Path configuration; - @Parameterized.Parameters(name = "{0}, access-modification: {1}") + @Parameters(name = "{0}, access-modification: {1}") public static List data() { return buildParameters( getTestParameters() @@ -116,25 +115,17 @@ public NoRelaxationForSerializableTest(TestParameters parameters, boolean access this.accessModification = accessModification; } - @Before - public void setUpConfiguration() throws Exception { - configuration = temp.newFile("pg.conf").toPath().toAbsolutePath(); - FileUtils.writeTextFile(configuration, StringUtils.lines( - keepMainProguardConfiguration(MAIN), - accessModification ? "-allowaccessmodification" : "" - )); - } - @Test public void testProguard_withKeepRules() throws Exception { assumeTrue(parameters.isCfRuntime()); - testForProguard() + testForProguard(ProguardVersion.getLatest()) .addProgramClasses(CLASSES) - .addKeepRuleFiles(configuration) + .addKeepMainRule(MAIN) .addKeepRules(KEEPMEMBER_RULES) .addInliningAnnotations() .addMemberValuePropagationAnnotations() .addNoVerticalClassMergingAnnotations() + .allowAccessModification(accessModification) .compile() .run(parameters.getRuntime(), MAIN) .assertSuccessWithOutput(EXPECTED_OUTPUT) @@ -143,34 +134,40 @@ public void testProguard_withKeepRules() throws Exception { @Test public void testR8_withKeepRules() throws Exception { - R8TestCompileResult result = - testForR8(parameters.getBackend()) - .addProgramClasses(CLASSES) - .addMemberValuePropagationAnnotations() - .addNoVerticalClassMergingAnnotations() - .enableInliningAnnotations() - .addKeepRuleFiles(configuration) - .addKeepRules(KEEPMEMBER_RULES) - .setMinApi(parameters) - .compile() - .inspect(this::inspect); - // TODO(b/117302947): Need to update ART binary. - if (parameters.isCfRuntime()) { - result - .run(parameters.getRuntime(), MAIN) - .assertSuccessWithOutput(EXPECTED_OUTPUT); - } + testForR8(parameters.getBackend()) + .addProgramClasses(CLASSES) + .addMemberValuePropagationAnnotations() + .addNoVerticalClassMergingAnnotations() + .enableInliningAnnotations() + .addKeepMainRule(MAIN) + .addKeepRules(KEEPMEMBER_RULES) + .allowAccessModification(accessModification) + .setMinApi(parameters) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), MAIN) + .applyIf( + parameters.isCfRuntime() + || parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V5_1_1, Version.V8_1_0) + || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V10_0_0), + runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT), + parameters.isDexRuntimeVersion(Version.V6_0_1) + || parameters.isDexRuntimeVersion(Version.V7_0_0), + runResult -> runResult.assertFailureWithErrorThatThrows(UnsatisfiedLinkError.class), + runResult -> + runResult.assertFailureWithErrorThatThrows(NoSuchAlgorithmException.class)); } @Test public void testProguard_withoutKeepRules() throws Exception { assumeTrue(parameters.isCfRuntime()); - testForProguard() + testForProguard(ProguardVersion.getLatest()) .addProgramClasses(CLASSES) .addInliningAnnotations() .addMemberValuePropagationAnnotations() .addNoVerticalClassMergingAnnotations() - .addKeepRuleFiles(configuration) + .addKeepMainRule(MAIN) + .allowAccessModification(accessModification) .compile() .run(parameters.getRuntime(), MAIN) .assertFailureWithErrorThatMatches(containsString("Could not deserialize")); @@ -178,21 +175,28 @@ public void testProguard_withoutKeepRules() throws Exception { @Test public void testR8_withoutKeepRules() throws Exception { - R8TestCompileResult result = - testForR8(parameters.getBackend()) - .addProgramClasses(CLASSES) - .addNoVerticalClassMergingAnnotations() - .enableInliningAnnotations() - .enableMemberValuePropagationAnnotations() - .addKeepRuleFiles(configuration) - .setMinApi(parameters) - .compile(); - // TODO(b/117302947): Need to update ART binary. - if (parameters.isCfRuntime()) { - result - .run(parameters.getRuntime(), MAIN) - .assertFailureWithErrorThatMatches(containsString("Could not deserialize")); - } + testForR8(parameters.getBackend()) + .addProgramClasses(CLASSES) + .addNoVerticalClassMergingAnnotations() + .enableInliningAnnotations() + .enableMemberValuePropagationAnnotations() + .addKeepMainRule(MAIN) + .allowAccessModification(accessModification) + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), MAIN) + .applyIf( + parameters.isCfRuntime() + || parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V5_1_1, Version.V8_1_0) + || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V10_0_0), + runResult -> + runResult.assertFailureWithErrorThatMatches( + containsString("Could not deserialize")), + parameters.isDexRuntimeVersion(Version.V6_0_1) + || parameters.isDexRuntimeVersion(Version.V7_0_0), + runResult -> runResult.assertFailureWithErrorThatThrows(UnsatisfiedLinkError.class), + runResult -> + runResult.assertFailureWithErrorThatThrows(NoSuchAlgorithmException.class)); } private void inspect(CodeInspector inspector) { diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java index 1c6d936da0..d41508e48c 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java @@ -46,36 +46,18 @@ public NonConstructorRelaxationTest( this.enableUnusedArgumentRemoval = enableUnusedArgumentRemoval; } + @Test + public void testStaticMethodRelaxationJvm() throws Exception { + parameters.assumeJvmTestParameters(); + testForJvm(parameters) + .addTestClasspath() + .run(parameters.getRuntime(), C.class) + .assertSuccessWithOutput(getExpectedOutputForStaticMethodRelaxationTest()); + } + @Test public void testStaticMethodRelaxation() throws Exception { - String expectedOutput = - StringUtils.lines( - "A::baz()", - "A::bar()", - "A::bar(int)", - "A::blah(int)", - "B::blah(int)", - "BB::blah(int)", - "A::foo()A::baz()A::bar()A::bar(int)", - "B::bar() >> java.lang.IllegalAccessError", - "java.lang.IllegalAccessError", - "A::foo()A::baz()A::bar()A::bar(int)", - "B::blah(int)", - "A::foo()A::baz()A::bar()A::bar(int)", - "B::bar() >> java.lang.IllegalAccessError", - "C::bar(int)java.lang.IllegalAccessErrorB::bar() >> " - + "java.lang.IllegalAccessErrorB::bar() >> java.lang.IllegalAccessError", - "B::foo()A::foo()A::baz()A::bar()A::bar(int)", - "C::blah(int)"); Class mainClass = C.class; - if (parameters.isCfRuntime()) { - // Only run JVM reference on CF runtimes. - testForJvm(parameters) - .addTestClasspath() - .run(parameters.getRuntime(), mainClass) - .assertSuccessWithOutput(expectedOutput); - } - R8TestRunResult result = testForR8(parameters.getBackend()) .addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage())) @@ -105,9 +87,8 @@ public void testStaticMethodRelaxation() throws Exception { "}") .allowAccessModification() .setMinApi(parameters) - .run(parameters.getRuntime(), mainClass); - - assertEquals(expectedOutput, result.getStdOut()); + .run(parameters.getRuntime(), mainClass) + .assertSuccessWithOutput(getExpectedOutputForStaticMethodRelaxationTest()); CodeInspector inspector = result.inspector(); @@ -121,13 +102,43 @@ public void testStaticMethodRelaxation() throws Exception { MethodSignature blahMethodSignatureAfterArgumentRemoval = new MethodSignature( - "blah", + enableUnusedArgumentRemoval ? "blah$1" : "blah", STRING, enableUnusedArgumentRemoval ? ImmutableList.of() : ImmutableList.of("int")); assertPublic(inspector, A.class, blahMethodSignatureAfterArgumentRemoval); assertPublic(inspector, BB.class, blahMethodSignatureAfterArgumentRemoval); } + private static String getExpectedOutputForStaticMethodRelaxationTest() { + return StringUtils.lines( + "A::baz()", + "A::bar()", + "A::bar(int)", + "A::blah(int)", + "B::blah(int)", + "BB::blah(int)", + "A::foo()A::baz()A::bar()A::bar(int)", + "B::bar() >> java.lang.IllegalAccessError", + "java.lang.IllegalAccessError", + "A::foo()A::baz()A::bar()A::bar(int)", + "B::blah(int)", + "A::foo()A::baz()A::bar()A::bar(int)", + "B::bar() >> java.lang.IllegalAccessError", + "C::bar(int)java.lang.IllegalAccessErrorB::bar() >> " + + "java.lang.IllegalAccessErrorB::bar() >> java.lang.IllegalAccessError", + "B::foo()A::foo()A::baz()A::bar()A::bar(int)", + "C::blah(int)"); + } + + @Test + public void testInstanceMethodRelaxationJvm() throws Exception { + parameters.assumeJvmTestParameters(); + testForJvm(parameters) + .addTestClasspath() + .run(parameters.getRuntime(), TestMain.class) + .assertSuccessWithOutput(getExpectedOutputForInstanceMethodRelaxationTest()); + } + @Test public void testInstanceMethodRelaxationWithVerticalClassMerging() throws Exception { testInstanceMethodRelaxation(true); @@ -139,29 +150,7 @@ public void testInstanceMethodRelaxationWithoutVerticalClassMerging() throws Exc } private void testInstanceMethodRelaxation(boolean enableVerticalClassMerging) throws Exception { - String expectedOutput = - StringUtils.lines( - "Base::foo()", - "Base::foo1()", - "Base::foo2()", - "Base::foo3()", - "Sub1::foo1()", - "Itf1::foo1(0) >> Sub1::foo1()", - "Sub1::bar1(0)", - "Sub1::foo3()", - "Sub2::foo2()", - "Itf2::foo2(0) >> Sub2::foo2()", - "Sub2::bar2(0)", - "Sub2::foo3()"); Class mainClass = TestMain.class; - if (parameters.isCfRuntime()) { - // Only run JVM reference on CF runtimes. - testForJvm(parameters) - .addTestClasspath() - .run(parameters.getRuntime(), mainClass) - .assertSuccessWithOutput(expectedOutput); - } - R8TestRunResult result = testForR8(parameters.getBackend()) .addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage())) @@ -188,7 +177,7 @@ private void testInstanceMethodRelaxation(boolean enableVerticalClassMerging) th .run(parameters.getRuntime(), mainClass); assertEquals( - expectedOutput, + getExpectedOutputForInstanceMethodRelaxationTest(), result .getStdOut() .replace("java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError")); @@ -199,10 +188,10 @@ private void testInstanceMethodRelaxation(boolean enableVerticalClassMerging) th CodeInspector codeInspector = result.inspector(); assertPublic(codeInspector, Base.class, new MethodSignature("foo", STRING, ImmutableList.of())); - // Base#foo?() can't be publicized due to Itf<1>#foo<1>(). - assertNotPublic( + // Base#foo?() is publicized by renaming due to Itf<1>#foo<1>(). + assertPublic( codeInspector, Base.class, new MethodSignature("foo1", STRING, ImmutableList.of())); - assertNotPublic( + assertPublic( codeInspector, Base.class, new MethodSignature("foo2", STRING, ImmutableList.of())); if (!enableVerticalClassMerging) { @@ -217,4 +206,20 @@ private void testInstanceMethodRelaxation(boolean enableVerticalClassMerging) th codeInspector, Sub2.class, new MethodSignature("bar2", STRING, ImmutableList.of("int"))); } } + + private static String getExpectedOutputForInstanceMethodRelaxationTest() { + return StringUtils.lines( + "Base::foo()", + "Base::foo1()", + "Base::foo2()", + "Base::foo3()", + "Sub1::foo1()", + "Itf1::foo1(0) >> Sub1::foo1()", + "Sub1::bar1(0)", + "Sub1::foo3()", + "Sub2::foo2()", + "Itf2::foo2(0) >> Sub2::foo2()", + "Sub2::bar2(0)", + "Sub2::foo3()"); + } } diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java index ea6a6a0680..82ceae16f8 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java @@ -28,6 +28,7 @@ public class PrivateOverrideOfPublicMethodTest extends TestBase { private static final String EXPECTED_OUTPUT_5_TO_6 = StringUtils.lines("A", "A", "A", "A", "A", "A"); private static final String EXPECTED_OUTPUT_7 = StringUtils.lines("A", "B", "A", "A", "B", "A"); + private static final String EXPECTED_OUTPUT_R8 = StringUtils.lines("A", "B", "B", "A", "B", "A"); private static byte[] programClassFileData; @@ -78,7 +79,8 @@ public void testR8() throws Exception { .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters) .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutput(EXPECTED_OUTPUT); + // TODO(b/278687711): Access modifier should preserve IllegalAccessErrors. + .assertSuccessWithOutput(EXPECTED_OUTPUT_R8); } static class Main { diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java index 4698b94805..ce6a9d0ab2 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java @@ -5,10 +5,9 @@ package com.android.tools.r8.accessrelaxation; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static com.android.tools.r8.utils.codeinspector.Matchers.isPrivate; import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; @@ -56,16 +55,16 @@ public void test() throws Exception { assertThat(aMethodSubject, isPresent()); assertThat(aMethodSubject, isPublic()); - // Check that B.foo is still private. + // Check that B.foo is publicized. ClassSubject bClassSubject = inspector.clazz(B.class); assertThat(bClassSubject, isPresent()); MethodSubject bMethodSubject = bClassSubject.uniqueMethodWithOriginalName("foo"); assertThat(bMethodSubject, isPresent()); - assertThat(bMethodSubject, isPrivate()); + assertThat(bMethodSubject, isPublic()); - // Verify that the two methods still have the same name. - assertEquals(aMethodSubject.getFinalName(), bMethodSubject.getFinalName()); + // Verify that the two methods are given different names. + assertNotEquals(aMethodSubject.getFinalName(), bMethodSubject.getFinalName()); }) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines("A", "B", "A", "A"); diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java index 0c25396dd0..dcc51cddaf 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java @@ -3,9 +3,11 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.accessrelaxation.privateinstance; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverPropagateValue; +@NeverClassInline public class Base { @NeverPropagateValue diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java index ffdda9803b..ab72433075 100644 --- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java +++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java @@ -4,11 +4,11 @@ package com.android.tools.r8.naming.b72391662; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import com.android.tools.r8.NeverPropagateValue; import com.android.tools.r8.ToolHelper; @@ -144,20 +144,16 @@ public void test_keepAll() throws Exception { // Test the totally unused method. MethodSubject staticMethod = testClass.uniqueMethodWithOriginalName("unused"); assertThat(staticMethod, isPresent()); + assertThat(staticMethod, isPackagePrivate()); assertEquals(minify, staticMethod.isRenamed()); - if (shrinker.isR8()) { - assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic()); - } else { - assertFalse(staticMethod.getMethod().accessFlags.isPublic()); - } // Test an indirectly referred method. staticMethod = testClass.uniqueMethodWithOriginalName("staticMethod"); assertThat(staticMethod, isPresent()); assertEquals(minify, staticMethod.isRenamed()); - boolean publicizeCondition = shrinker.isR8() ? allowAccessModification - : minify && repackagePrefix != null && allowAccessModification; - assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic()); + boolean publicizeCondition = + shrinker.isProguard() && minify && repackagePrefix != null && allowAccessModification; + assertEquals(publicizeCondition, staticMethod.getMethod().isPublic()); } @Test @@ -195,20 +191,16 @@ public void test_keepNonPublic() throws Exception { // Test the totally unused method. MethodSubject staticMethod = testClass.uniqueMethodWithOriginalName("unused"); assertThat(staticMethod, isPresent()); + assertThat(staticMethod, isPackagePrivate()); assertEquals(minify, staticMethod.isRenamed()); - if (shrinker.isR8()) { - assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic()); - } else { - assertFalse(staticMethod.getMethod().accessFlags.isPublic()); - } // Test an indirectly referred method. staticMethod = testClass.uniqueMethodWithOriginalName("staticMethod"); assertThat(staticMethod, isPresent()); assertEquals(minify, staticMethod.isRenamed()); - boolean publicizeCondition = shrinker.isR8() ? allowAccessModification - : minify && repackagePrefix != null && allowAccessModification; - assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic()); + boolean publicizeCondition = + shrinker.isProguard() && minify && repackagePrefix != null && allowAccessModification; + assertEquals(publicizeCondition, staticMethod.getMethod().isPublic()); } @Test diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java index 9aec797cc5..3f426a58db 100644 --- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java @@ -3,10 +3,10 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.shaking.ifrule; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; @@ -176,7 +176,7 @@ public void ifOnNonPublic_keepOnNonPublic() throws Exception { assertThat(methodSubject, not(isPresent())); methodSubject = classSubject.uniqueMethodWithOriginalName("nonPublicMethod"); assertThat(methodSubject, isPresent()); - assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic()); + assertThat(methodSubject, isPackagePrivate()); }); } @@ -255,7 +255,7 @@ public void ifOnPublic_keepOnNonPublic() throws Exception { assertThat(methodSubject, not(isPresent())); methodSubject = classSubject.uniqueMethodWithOriginalName("nonPublicMethod"); assertThat(methodSubject, isPresent()); - assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic()); + assertThat(methodSubject, isPackagePrivate()); }); } } From d2732c9c0b3812069ad0dd1592cc77cb16c13552 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Wed, 14 Jun 2023 20:28:18 +0200 Subject: [PATCH 111/153] Remove legacy access modifier Change-Id: I46c2581ea42a5f002c2ff983732511316e326631 --- src/main/java/com/android/tools/r8/R8.java | 10 - .../r8/optimize/LegacyAccessModifier.java | 218 ------------------ .../accessmodification/AccessModifier.java | 2 +- .../AccessModifierOptions.java | 7 - 4 files changed, 1 insertion(+), 236 deletions(-) delete mode 100644 src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 1684062512..56d0a8c992 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -62,7 +62,6 @@ import com.android.tools.r8.naming.ProguardMapMinifier; import com.android.tools.r8.naming.RecordRewritingNamingLens; import com.android.tools.r8.naming.signature.GenericSignatureRewriter; -import com.android.tools.r8.optimize.LegacyAccessModifier; import com.android.tools.r8.optimize.MemberRebindingAnalysis; import com.android.tools.r8.optimize.MemberRebindingIdentityLens; import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory; @@ -449,15 +448,6 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO // to clear the cache, so that we will recompute the type lattice elements. appView.dexItemFactory().clearTypeElementsCache(); - // TODO(b/132677331): Remove legacy access modifier. - LegacyAccessModifier.run(appViewWithLiveness, executorService, timing); - if (appView.graphLens().isPublicizerLens()) { - // We can now remove redundant bridges. Note that we do not need to update the - // invoke-targets here, as the existing invokes will simply dispatch to the now - // visible super-method. MemberRebinding, if run, will then dispatch it correctly. - new RedundantBridgeRemover(appView.withLiveness()).run(null, executorService, timing); - } - // This pass attempts to reduce the number of nests and nest size to allow further passes, and // should therefore be run after the publicizer. new NestReducer(appViewWithLiveness).run(executorService, timing); diff --git a/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java b/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java deleted file mode 100644 index 2c82527092..0000000000 --- a/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) 2016, 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.optimize; - -import static com.android.tools.r8.dex.Constants.ACC_PRIVATE; -import static com.android.tools.r8.dex.Constants.ACC_PROTECTED; -import static com.android.tools.r8.dex.Constants.ACC_PUBLIC; -import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.FieldAccessFlags; -import com.android.tools.r8.graph.InnerClassAttribute; -import com.android.tools.r8.graph.MethodAccessFlags; -import com.android.tools.r8.graph.ProgramDefinition; -import com.android.tools.r8.graph.ProgramField; -import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.graph.SubtypingInfo; -import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool; -import com.android.tools.r8.ir.optimize.MethodPoolCollection; -import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.MethodSignatureEquivalence; -import com.android.tools.r8.utils.OptionalBool; -import com.android.tools.r8.utils.Timing; -import com.google.common.base.Equivalence.Wrapper; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; - -public final class LegacyAccessModifier { - - private final AppView appView; - private final SubtypingInfo subtypingInfo; - private final MethodPoolCollection methodPoolCollection; - - private final PublicizedLensBuilder lensBuilder = PublicizerLens.createBuilder(); - - private LegacyAccessModifier(AppView appView) { - this.appView = appView; - this.subtypingInfo = appView.appInfo().computeSubtypingInfo(); - this.methodPoolCollection = - // We will add private instance methods when we promote them. - new MethodPoolCollection( - appView, subtypingInfo, MethodPoolCollection::excludesPrivateInstanceMethod); - } - - /** - * Marks all package private and protected methods and fields as public. Makes all private static - * methods public. Makes private instance methods public final instance methods, if possible. - * - *

This will destructively update the DexApplication passed in as argument. - */ - public static void run( - AppView appView, ExecutorService executorService, Timing timing) - throws ExecutionException { - InternalOptions options = appView.options(); - if (options.isAccessModificationEnabled() - && !options.getAccessModifierOptions().isExperimentalAccessModificationEnabled()) { - timing.begin("Access modification"); - new LegacyAccessModifier(appView).internalRun(executorService, timing); - timing.end(); - } - } - - private void internalRun(ExecutorService executorService, Timing timing) - throws ExecutionException { - // Phase 1: Collect methods to check if private instance methods don't have conflicts. - methodPoolCollection.buildAll(executorService, timing); - - // Phase 2: Visit classes and promote class/member to public if possible. - timing.begin("Phase 2: promoteToPublic"); - appView.appInfo().forEachReachableInterface(clazz -> processType(clazz.getType())); - processType(appView.dexItemFactory().objectType); - timing.end(); - - PublicizerLens publicizerLens = lensBuilder.build(appView); - if (publicizerLens != null) { - appView.setGraphLens(publicizerLens); - } - - appView.notifyOptimizationFinishedForTesting(); - } - - private void doPublicize(ProgramDefinition definition) { - definition.getAccessFlags().promoteToPublic(); - } - - private void processType(DexType type) { - DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); - if (clazz != null) { - processClass(clazz); - } - subtypingInfo.forAllImmediateExtendsSubtypes(type, this::processType); - } - - private void processClass(DexProgramClass clazz) { - if (appView.appInfo().isAccessModificationAllowed(clazz)) { - doPublicize(clazz); - } - - // Publicize fields. - clazz.forEachProgramField(this::processField); - - // Publicize methods. - Set privateInstanceMethods = new LinkedHashSet<>(); - clazz.forEachProgramMethod( - method -> { - if (publicizeMethod(method)) { - privateInstanceMethods.add(method.getDefinition()); - } - }); - if (!privateInstanceMethods.isEmpty()) { - clazz.virtualizeMethods(privateInstanceMethods); - } - - // Publicize inner class attribute. - InnerClassAttribute attr = clazz.getInnerClassAttributeForThisClass(); - if (attr != null) { - int accessFlags = ((attr.getAccess() | ACC_PUBLIC) & ~ACC_PRIVATE) & ~ACC_PROTECTED; - clazz.replaceInnerClassAttributeForThisClass( - new InnerClassAttribute( - accessFlags, attr.getInner(), attr.getOuter(), attr.getInnerName())); - } - } - - private void processField(ProgramField field) { - if (appView.appInfo().isAccessModificationAllowed(field)) { - publicizeField(field); - } - } - - private void publicizeField(ProgramField field) { - FieldAccessFlags flags = field.getAccessFlags(); - if (!flags.isPublic()) { - flags.promoteToPublic(); - } - } - - private boolean publicizeMethod(ProgramMethod method) { - MethodAccessFlags accessFlags = method.getAccessFlags(); - if (accessFlags.isPublic()) { - return false; - } - // If this method is mentioned in keep rules, do not transform (rule applications changed). - DexEncodedMethod definition = method.getDefinition(); - if (!appView.appInfo().isAccessModificationAllowed(method)) { - // TODO(b/131130038): Also do not publicize package-private and protected methods that are - // kept. - if (definition.isPrivate()) { - return false; - } - } - - if (method.getDefinition().isInstanceInitializer() || accessFlags.isProtected()) { - doPublicize(method); - return false; - } - - if (accessFlags.isPackagePrivate()) { - // If we publicize a package private method we have to ensure there is no overrides of it. We - // could potentially publicize a method if it only has package-private overrides. - // TODO(b/182136236): See if we can break the hierarchy for clusters. - MemberPool memberPool = methodPoolCollection.get(method.getHolder()); - Wrapper methodKey = MethodSignatureEquivalence.get().wrap(method.getReference()); - if (memberPool.below( - methodKey, - false, - true, - (clazz, ignored) -> - !method.getContextType().getPackageName().equals(clazz.getType().getPackageName()))) { - return false; - } - doPublicize(method); - return false; - } - - assert accessFlags.isPrivate(); - - if (accessFlags.isStatic()) { - // For private static methods we can just relax the access to public, since - // even though JLS prevents from declaring static method in derived class if - // an instance method with same signature exists in superclass, JVM actually - // does not take into account access of the static methods. - doPublicize(method); - return false; - } - - // We can't publicize private instance methods in interfaces or methods that are copied from - // interfaces to lambda-desugared classes because this will be added as a new default method. - // TODO(b/111118390): It might be possible to transform it into static methods, though. - if (method.getHolder().isInterface() || accessFlags.isSynthetic()) { - return false; - } - - boolean wasSeen = methodPoolCollection.markIfNotSeen(method.getHolder(), method.getReference()); - if (wasSeen) { - // We can't do anything further because even renaming is not allowed due to the keep rule. - if (!appView.appInfo().isMinificationAllowed(method)) { - return false; - } - // TODO(b/111118390): Renaming will enable more private instance methods to be publicized. - return false; - } - lensBuilder.add(method.getReference()); - accessFlags.promoteToFinal(); - doPublicize(method); - // The method just became public and is therefore not a library override. - definition.setLibraryMethodOverride(OptionalBool.FALSE); - return true; - } -} diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java index b7342dcb2d..ea25b09a88 100644 --- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java @@ -63,7 +63,7 @@ public static void run( AppView appView, ExecutorService executorService, Timing timing) throws ExecutionException { timing.begin("Access modification"); - if (appView.options().getAccessModifierOptions().isExperimentalAccessModificationEnabled()) { + if (appView.options().getAccessModifierOptions().isAccessModificationEnabled()) { new AccessModifier(appView) .processStronglyConnectedComponents(executorService) .installLens(executorService, timing); diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java index e944d38e50..a9d69d6159 100644 --- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java @@ -8,8 +8,6 @@ public class AccessModifierOptions { - private boolean enableExperimentalAccessModification = true; - private InternalOptions options; public AccessModifierOptions(InternalOptions options) { @@ -20,9 +18,4 @@ public boolean isAccessModificationEnabled() { return options.hasProguardConfiguration() && options.getProguardConfiguration().isAccessModificationAllowed(); } - - public boolean isExperimentalAccessModificationEnabled() { - // TODO(b/132677331): Do not require -allowaccessmodification in R8 full mode. - return isAccessModificationEnabled() && enableExperimentalAccessModification; - } } From a046083828eaf3a7878b9ff9db15ca28f0f9b78b Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Wed, 14 Jun 2023 20:28:27 +0200 Subject: [PATCH 112/153] Remove remaining use of MemberPoolCollection Change-Id: Ifa10de772c550f89faf0815469492ba14f7954e3 --- .../tools/r8/graph/DexItemFactory.java | 16 +- .../com/android/tools/r8/graph/DexMethod.java | 2 +- .../com/android/tools/r8/graph/DexProto.java | 7 + .../ir/desugar/BackportedMethodRewriter.java | 4 +- .../DesugaredLibraryConversionCfProvider.java | 5 +- .../r8/ir/optimize/MemberPoolCollection.java | 276 ------------------ .../r8/ir/optimize/MethodPoolCollection.java | 80 ----- .../tools/r8/shaking/VerticalClassMerger.java | 29 +- 8 files changed, 23 insertions(+), 396 deletions(-) delete mode 100644 src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java delete mode 100644 src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 41ca6f58a9..854ba5ba26 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java @@ -2979,28 +2979,14 @@ public DexProto createProto(DexType returnType, List parameters) { return createProto(returnType, parameters.toArray(DexType.EMPTY_ARRAY)); } - public DexProto protoWithDifferentFirstParameter(DexProto proto, DexType firstParameter) { - DexType[] parameterTypes = proto.parameters.values.clone(); - parameterTypes[0] = firstParameter; - return createProto(proto.returnType, parameterTypes); - } - public DexProto prependHolderToProto(DexMethod method) { - return prependTypeToProto(method.holder, method.proto); + return method.getProto().prependParameter(method.getHolderType(), this); } public DexProto prependHolderToProtoIf(DexMethod method, boolean condition) { return condition ? prependHolderToProto(method) : method.getProto(); } - public DexProto prependTypeToProto(DexType extraFirstType, DexProto initialProto) { - DexType[] parameterTypes = new DexType[initialProto.parameters.size() + 1]; - parameterTypes[0] = extraFirstType; - System.arraycopy( - initialProto.parameters.values, 0, parameterTypes, 1, initialProto.parameters.size()); - return createProto(initialProto.returnType, parameterTypes); - } - public DexProto appendTypeToProto(DexProto initialProto, DexType extraLastType) { DexType[] parameterTypes = new DexType[initialProto.parameters.size() + 1]; System.arraycopy( diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java index fe2f81d2b2..1ad1e1bea3 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java @@ -325,7 +325,7 @@ public boolean isInstanceInitializerInlineIntoOrMerged(AppView appView) { public DexMethod withExtraArgumentPrepended(DexType type, DexItemFactory dexItemFactory) { return dexItemFactory.createMethod( - holder, dexItemFactory.prependTypeToProto(type, proto), name); + holder, getProto().prependParameter(type, dexItemFactory), name); } public DexMethod withHolder(DexDefinition definition, DexItemFactory dexItemFactory) { diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java index 80ff41d0cb..b85f0dae10 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProto.java +++ b/src/main/java/com/android/tools/r8/graph/DexProto.java @@ -91,6 +91,13 @@ public int getArity() { return parameters.size(); } + public DexProto prependParameter(DexType parameter, DexItemFactory dexItemFactory) { + DexType[] parameterTypes = new DexType[getParameters().size() + 1]; + parameterTypes[0] = parameter; + System.arraycopy(getParameters().getBacking(), 0, parameterTypes, 1, getParameters().size()); + return dexItemFactory.createProto(getReturnType(), parameterTypes); + } + @Override public String toString() { return "Proto " + shorty + " " + returnType + " " + parameters; diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java index f715e77cf0..bfcb585797 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java @@ -1910,8 +1910,8 @@ private static class StatifyingMethodGenerator extends MethodGenerator { } @Override - public DexProto getProto(DexItemFactory itemFactory) { - return itemFactory.prependTypeToProto(receiverType, super.getProto(itemFactory)); + public DexProto getProto(DexItemFactory factory) { + return method.getProto().prependParameter(receiverType, factory); } } diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java index 9a75734111..5460d4f7c1 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java @@ -225,10 +225,7 @@ public ProgramMethod generateOutlinedAPIConversion( ProgramMethod context, MethodProcessingContext methodProcessingContext) { DexMethod method = invoke.getMethod(); - DexProto newProto = - invoke.isInvokeStatic() - ? method.proto - : factory.prependTypeToProto(method.getHolderType(), method.getProto()); + DexProto newProto = factory.prependHolderToProtoIf(method, !invoke.isInvokeStatic()); DexMethod returnConversion = computeReturnConversion( method, false, eventConsumer, context, methodProcessingContext::createUniqueContext); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java deleted file mode 100644 index bd0376b1a9..0000000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) 2019, 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.ir.optimize; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexMember; -import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.SubtypingInfo; -import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.ThreadUtils; -import com.android.tools.r8.utils.Timing; -import com.android.tools.r8.utils.WorkList; -import com.google.common.base.Equivalence; -import com.google.common.base.Equivalence.Wrapper; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.function.BiFunction; -import java.util.function.Predicate; - -// Per-class collection of member signatures. -public abstract class MemberPoolCollection> { - - final Equivalence equivalence; - final AppView appView; - final SubtypingInfo subtypingInfo; - final Map> memberPools = new ConcurrentHashMap<>(); - - MemberPoolCollection( - AppView appView, - Equivalence equivalence, - SubtypingInfo subtypingInfo) { - this.appView = appView; - this.equivalence = equivalence; - this.subtypingInfo = subtypingInfo; - } - - public void buildAll(ExecutorService executorService, Timing timing) throws ExecutionException { - timing.begin("Building member pool collection"); - try { - List> futures = new ArrayList<>(); - - // Generate a future for each class that will build the member pool collection for the - // corresponding class. Note that, we visit the classes using a top-down class hierarchy - // traversal, since this ensures that we do not visit library classes that are not - // reachable from any program class. - TopDownClassHierarchyTraversal.forAllClasses(appView) - .visit(appView.appInfo().classes(), clazz -> submit(clazz, futures, executorService)); - ThreadUtils.awaitFutures(futures); - } finally { - timing.end(); - } - } - - public MemberPool buildForHierarchy( - DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException { - timing.begin("Building member pool collection"); - try { - List> futures = new ArrayList<>(); - submitAll( - getAllSuperTypesInclusive(clazz, memberPools::containsKey), futures, executorService); - submitAll(getAllSubTypesExclusive(clazz, memberPools::containsKey), futures, executorService); - ThreadUtils.awaitFutures(futures); - } finally { - timing.end(); - } - return get(clazz); - } - - public boolean hasPool(DexClass clazz) { - return memberPools.containsKey(clazz); - } - - public MemberPool get(DexClass clazz) { - assert hasPool(clazz); - return memberPools.get(clazz); - } - - public boolean markIfNotSeen(DexClass clazz, R reference) { - MemberPool memberPool = get(clazz); - Wrapper key = equivalence.wrap(reference); - if (memberPool.hasSeen(key)) { - return true; - } - memberPool.seen(key); - return false; - } - - private void submitAll( - Iterable classes, - List> futures, - ExecutorService executorService) { - for (DexClass clazz : classes) { - submit(clazz, futures, executorService); - } - } - - private void submit(DexClass clazz, List> futures, ExecutorService executorService) { - futures.add(executorService.submit(computeMemberPoolForClass(clazz))); - } - - abstract Runnable computeMemberPoolForClass(DexClass clazz); - - private Set getAllSuperTypesInclusive( - DexClass subject, Predicate stoppingCriterion) { - Set superTypes = new HashSet<>(); - Deque worklist = new ArrayDeque<>(); - worklist.add(subject); - while (!worklist.isEmpty()) { - DexClass clazz = worklist.pop(); - if (stoppingCriterion.test(clazz)) { - continue; - } - if (superTypes.add(clazz)) { - if (clazz.superType != null) { - addNonNull(worklist, appView.definitionFor(clazz.superType)); - } - for (DexType interfaceType : clazz.interfaces.values) { - addNonNull(worklist, appView.definitionFor(interfaceType)); - } - } - } - return superTypes; - } - - private Set getAllSubTypesExclusive( - DexClass subject, Predicate stoppingCriterion) { - Set subTypes = new HashSet<>(); - Deque worklist = new ArrayDeque<>(); - subtypingInfo.forAllImmediateExtendsSubtypes( - subject.type, type -> addNonNull(worklist, appView.definitionFor(type))); - subtypingInfo.forAllImmediateImplementsSubtypes( - subject.type, type -> addNonNull(worklist, appView.definitionFor(type))); - while (!worklist.isEmpty()) { - DexClass clazz = worklist.pop(); - if (stoppingCriterion.test(clazz)) { - continue; - } - if (subTypes.add(clazz)) { - subtypingInfo.forAllImmediateExtendsSubtypes( - clazz.type, type -> addNonNull(worklist, appView.definitionFor(type))); - subtypingInfo.forAllImmediateImplementsSubtypes( - clazz.type, type -> addNonNull(worklist, appView.definitionFor(type))); - } - } - return subTypes; - } - - public static class MemberPool { - - private final DexClass clazz; - private final Equivalence equivalence; - private MemberPool superType; - private final Set> interfaces = new HashSet<>(); - private final Set> subTypes = new HashSet<>(); - private final Set> memberPool = new HashSet<>(); - - MemberPool(Equivalence equivalence, DexClass clazz) { - this.equivalence = equivalence; - this.clazz = clazz; - } - - synchronized void linkSupertype(MemberPool superType) { - assert this.superType == null; - this.superType = superType; - } - - synchronized void linkSubtype(MemberPool subType) { - boolean added = subTypes.add(subType); - assert added; - } - - synchronized void linkInterface(MemberPool itf) { - boolean added = interfaces.add(itf); - assert added; - } - - public void seen(T member) { - seen(equivalence.wrap(member)); - } - - public synchronized void seen(Wrapper member) { - boolean added = memberPool.add(member); - assert added; - } - - public boolean hasSeen(Wrapper member) { - return fold(member, false, true, (t, ignored) -> true); - } - - public boolean hasSeenDirectly(Wrapper member) { - return here(member, false, (t, ignored) -> true); - } - - public boolean hasSeenStrictlyAbove(Wrapper member) { - return above(member, false, false, true, (t, ignored) -> true); - } - - public boolean hasSeenStrictlyBelow(Wrapper member) { - return below(member, false, true, (t, ignored) -> true); - } - - private S above( - Wrapper member, - boolean inclusive, - S value, - S terminator, - BiFunction accumulator) { - WorkList> workList = WorkList.newIdentityWorkList(this); - while (workList.hasNext()) { - MemberPool next = workList.next(); - if (inclusive) { - value = next.here(member, value, accumulator); - if (value == terminator) { - return value; - } - } - inclusive = true; - if (next.superType != null) { - workList.addIfNotSeen(next.superType); - } - workList.addIfNotSeen(next.interfaces); - } - return value; - } - - private S here(Wrapper member, S value, BiFunction accumulator) { - if (memberPool.contains(member)) { - return accumulator.apply(clazz, value); - } - return value; - } - - public S below( - Wrapper member, S value, S terminator, BiFunction accumulator) { - WorkList> workList = WorkList.newIdentityWorkList(this.subTypes); - while (workList.hasNext()) { - MemberPool next = workList.next(); - value = next.here(member, value, accumulator); - if (value == terminator) { - return value; - } - workList.addIfNotSeen(next.interfaces); - workList.addIfNotSeen(next.subTypes); - } - return value; - } - - public S fold( - Wrapper member, S initialValue, S terminator, BiFunction accumulator) { - S value = above(member, true, initialValue, terminator, accumulator); - if (value == terminator) { - return value; - } - return below(member, initialValue, terminator, accumulator); - } - } - - private static void addNonNull(Collection collection, T item) { - if (item != null) { - collection.add(item); - } - } -} diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java deleted file mode 100644 index 78eb3432c4..0000000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java +++ /dev/null @@ -1,80 +0,0 @@ -// 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.ir.optimize; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.SubtypingInfo; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.MethodSignatureEquivalence; -import com.google.common.base.Predicates; -import java.util.function.Predicate; - -// Per-class collection of method signatures. -// -// Example use cases: -// *) in publicizer, -// to determine if a private method does not collide with methods in that class hierarchy. -// *) in vertical class merger, -// before moving a default interface method to its subtype, check if it does not collide with one -// in the given class hierarchy. -public class MethodPoolCollection extends MemberPoolCollection { - - private final Predicate methodTester; - - public MethodPoolCollection(AppView appView, SubtypingInfo subtypingInfo) { - this(appView, subtypingInfo, Predicates.alwaysTrue()); - } - - public MethodPoolCollection( - AppView appView, - SubtypingInfo subtypingInfo, - Predicate methodTester) { - super(appView, MethodSignatureEquivalence.get(), subtypingInfo); - this.methodTester = methodTester; - } - - public static boolean excludesPrivateInstanceMethod(DexEncodedMethod method) { - return !method.isPrivateMethod() || method.isStatic(); - } - - @Override - Runnable computeMemberPoolForClass(DexClass clazz) { - return () -> { - MemberPool methodPool = - memberPools.computeIfAbsent(clazz, k -> new MemberPool<>(equivalence, k)); - clazz.forEachMethod( - encodedMethod -> { - if (methodTester.test(encodedMethod)) { - methodPool.seen(equivalence.wrap(encodedMethod.getReference())); - } - }); - if (clazz.superType != null) { - DexClass superClazz = appView.definitionFor(clazz.superType); - if (superClazz != null) { - MemberPool superPool = - memberPools.computeIfAbsent( - superClazz, k -> new MemberPool<>(equivalence, superClazz)); - superPool.linkSubtype(methodPool); - methodPool.linkSupertype(superPool); - } - } - if (clazz.isInterface()) { - for (DexType subtype : subtypingInfo.allImmediateSubtypes(clazz.type)) { - DexClass subClazz = appView.definitionFor(subtype); - if (subClazz != null) { - MemberPool childPool = - memberPools.computeIfAbsent(subClazz, k -> new MemberPool<>(equivalence, subClazz)); - methodPool.linkSubtype(childPool); - childPool.linkInterface(methodPool); - } - } - } - }; - } -} diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index 4c152b6f98..eda8554d02 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java @@ -30,6 +30,7 @@ import com.android.tools.r8.graph.DexEncodedMember; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMember; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -68,8 +69,6 @@ import com.android.tools.r8.ir.code.InvokeType; import com.android.tools.r8.ir.code.Position.SyntheticPosition; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; -import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool; -import com.android.tools.r8.ir.optimize.MethodPoolCollection; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode; @@ -157,7 +156,6 @@ private enum Rename { private final InternalOptions options; private final SubtypingInfo subtypingInfo; private final ExecutorService executorService; - private final MethodPoolCollection methodPoolCollection; private final Timing timing; private Collection invokes; private final AndroidApiLevelCompute apiLevelCompute; @@ -197,7 +195,6 @@ public VerticalClassMerger( this.mainDexInfo = appInfo.getMainDexInfo(); this.subtypingInfo = appInfo.computeSubtypingInfo(); this.executorService = executorService; - this.methodPoolCollection = new MethodPoolCollection(appView, subtypingInfo); this.lensBuilder = new VerticalClassMergerGraphLens.Builder(appView.dexItemFactory()); this.apiLevelCompute = appView.apiLevelCompute(); this.timing = timing; @@ -998,21 +995,17 @@ assert restoreDebuggingState( // due to the way invoke-super works on default interface methods. In order to be able // to hit this method directly after the merge, we need to make it public, and find a // method name that does not collide with one in the hierarchy of this class. - MemberPool methodPoolForTarget = - methodPoolCollection.buildForHierarchy(target, executorService, timing); - resultingMethod = - renameMethod( - virtualMethod, - method -> - availableMethodSignatures.test(method) - && !methodPoolForTarget.hasSeen( - MethodSignatureEquivalence.get().wrap(method)), - Rename.ALWAYS, - appView.dexItemFactory().prependHolderToProto(virtualMethod.getReference())); + DexItemFactory dexItemFactory = appView.dexItemFactory(); + String resultingMethodBaseName = + virtualMethod.getName().toString() + '$' + source.getTypeName().replace('.', '$'); + DexMethod resultingMethodReference = + dexItemFactory.createMethod( + target.getType(), + virtualMethod.getProto().prependParameter(source.getType(), dexItemFactory), + dexItemFactory.createGloballyFreshMemberString(resultingMethodBaseName)); + assert availableMethodSignatures.test(resultingMethodReference); + resultingMethod = virtualMethod.toTypeSubstitutedMethod(resultingMethodReference); makeStatic(resultingMethod); - - // Update method pool collection now that we are adding a new public method. - methodPoolForTarget.seen(resultingMethod.getReference()); } else { // This virtual method could be called directly from a sub class via an invoke-super in- // struction. Therefore, we translate this virtual method into an instance method with a From 1ffa1d4bdf8721b23762fbe89df9dcc85fbb730b Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 15 Jun 2023 05:00:42 +0200 Subject: [PATCH 113/153] Update expected exception type in test Change-Id: Ic811ec2fe3c2a04318802251061efcb09b982fe5 --- .../NoRelaxationForSerializableTest.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java index 7fcbf6d0a3..87a0a49aff 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java @@ -151,8 +151,10 @@ public void testR8_withKeepRules() throws Exception { || parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V5_1_1, Version.V8_1_0) || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V10_0_0), runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT), - parameters.isDexRuntimeVersion(Version.V6_0_1) - || parameters.isDexRuntimeVersion(Version.V7_0_0), + parameters.isDexRuntimeVersion(Version.DEFAULT) + || parameters.isDexRuntimeVersion(Version.V6_0_1) + || parameters.isDexRuntimeVersion(Version.V7_0_0) + || parameters.isDexRuntimeVersion(Version.V9_0_0), runResult -> runResult.assertFailureWithErrorThatThrows(UnsatisfiedLinkError.class), runResult -> runResult.assertFailureWithErrorThatThrows(NoSuchAlgorithmException.class)); @@ -192,8 +194,10 @@ public void testR8_withoutKeepRules() throws Exception { runResult -> runResult.assertFailureWithErrorThatMatches( containsString("Could not deserialize")), - parameters.isDexRuntimeVersion(Version.V6_0_1) - || parameters.isDexRuntimeVersion(Version.V7_0_0), + parameters.isDexRuntimeVersion(Version.DEFAULT) + || parameters.isDexRuntimeVersion(Version.V6_0_1) + || parameters.isDexRuntimeVersion(Version.V7_0_0) + || parameters.isDexRuntimeVersion(Version.V9_0_0), runResult -> runResult.assertFailureWithErrorThatThrows(UnsatisfiedLinkError.class), runResult -> runResult.assertFailureWithErrorThatThrows(NoSuchAlgorithmException.class)); From 4443ca34416273e470232d63eef1573357c3e881 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 15 Jun 2023 12:24:57 +0200 Subject: [PATCH 114/153] Refactor lenses to describe their one-step mapping only We previously introduced the methods getPreviousMethodSignature() and getNextMethodSignature(), which describe the mapping made by a single lens. These are used to implement getOriginalMethodSignature() and getRenamedMethodSignature(), so that each lens does not need to add an implementation that recurses into the previous lens. This CL finishes this cleanup by introducing the methods getPreviousClassType(), getNextClassType(), getPreviousFieldSignature() and getNextFieldSignature(), and using these to implement getOriginalClassType(), lookupType(), getOriginalFieldSignature() and getRenamedFieldSignature(). Change-Id: I1e4f6df8feb08a52d979572d453735a610a626e5 --- .../tools/r8/graph/DexEncodedMethod.java | 12 ++ .../android/tools/r8/graph/ProgramMethod.java | 16 ++ .../classmerging/VerticallyMergedClasses.java | 9 +- .../tools/r8/graph/lens/AppliedGraphLens.java | 32 ++-- .../lens/ClearCodeRewritingGraphLens.java | 12 -- .../lens/DefaultNonIdentityGraphLens.java | 31 ++-- .../tools/r8/graph/lens/GraphLens.java | 167 +++++++++--------- .../r8/graph/lens/IdentityGraphLens.java | 26 --- .../tools/r8/graph/lens/NestedGraphLens.java | 84 +++------ .../r8/graph/lens/NonIdentityGraphLens.java | 31 ++-- .../HorizontalClassMergerGraphLens.java | 9 +- .../HorizontallyMergedClasses.java | 19 +- .../r8/ir/desugar/itf/InterfaceProcessor.java | 10 +- .../ir/optimize/enums/EnumUnboxingLens.java | 8 +- .../ArgumentPropagatorApplicationFixer.java | 2 +- .../ArgumentPropagatorGraphLens.java | 15 -- ...tPropagatorMethodReprocessingEnqueuer.java | 2 +- .../bridgehoisting/BridgeHoistingLens.java | 7 - .../RedundantBridgeRemovalLens.java | 9 +- .../tools/r8/repackaging/RepackagingLens.java | 15 +- .../tools/r8/shaking/VerticalClassMerger.java | 23 ++- .../shaking/VerticalClassMergerGraphLens.java | 7 +- .../r8/synthesis/SyntheticFinalization.java | 10 +- .../com/android/tools/r8/utils/LensUtils.java | 3 +- .../BidirectionalOneToOneHashMap.java | 4 + .../utils/collections/ProgramMethodSet.java | 3 +- .../collections/SortedProgramMethodSet.java | 6 +- 27 files changed, 255 insertions(+), 317 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index a45d54277f..67082ae5b4 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java @@ -43,6 +43,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind; import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; +import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.graph.proto.ArgumentInfoCollection; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.ValueType; @@ -1353,6 +1354,17 @@ public DexWritableCode getDexWritableCodeOrNull() { return code == null ? null : code.asDexWritableCode(); } + public DexEncodedMethod rewrittenWithLens( + GraphLens lens, GraphLens appliedLens, DexDefinitionSupplier definitions) { + assert this != SENTINEL; + DexMethod newMethodReference = lens.getRenamedMethodSignature(getReference(), appliedLens); + DexClass newHolder = definitions.definitionFor(newMethodReference.getHolderType()); + assert newHolder != null; + DexEncodedMethod newMethod = newHolder.lookupMethod(newMethodReference); + assert newMethod != null; + return newMethod; + } + public static Builder syntheticBuilder() { return new Builder(true); } diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java index c1f869cc7a..99879123c2 100644 --- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java +++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java @@ -201,4 +201,20 @@ public boolean keepLocals(AppView appView) { } return appView.options().debug || getOrComputeReachabilitySensitive(appView); } + + public ProgramMethod rewrittenWithLens( + GraphLens lens, GraphLens appliedLens, DexDefinitionSupplier definitions) { + DexMethod newMethod = lens.getRenamedMethodSignature(getReference(), appliedLens); + if (newMethod == getReference() && !getDefinition().isObsolete()) { + assert verifyIsConsistentWithLookup(definitions); + return this; + } + return asProgramMethodOrNull(definitions.definitionFor(newMethod)); + } + + private boolean verifyIsConsistentWithLookup(DexDefinitionSupplier definitions) { + DexClassAndMethod lookupMethod = definitions.definitionFor(getReference()); + assert getDefinition() == lookupMethod.getDefinition(); + return true; + } } diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java index ca193751e6..78c6259de2 100644 --- a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java +++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java @@ -8,6 +8,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap; import java.util.Collection; import java.util.Map; @@ -16,11 +17,11 @@ public class VerticallyMergedClasses implements MergedClasses { - private final BidirectionalManyToOneMap mergedClasses; + private final BidirectionalManyToOneRepresentativeMap mergedClasses; private final BidirectionalManyToOneMap mergedInterfaces; public VerticallyMergedClasses( - BidirectionalManyToOneMap mergedClasses, + BidirectionalManyToOneRepresentativeMap mergedClasses, BidirectionalManyToOneMap mergedInterfaces) { this.mergedClasses = mergedClasses; this.mergedInterfaces = mergedInterfaces; @@ -37,6 +38,10 @@ public void forEachMergeGroup(BiConsumer, DexType> consumer) { mergedClasses.forEachManyToOneMapping(consumer); } + public BidirectionalManyToOneRepresentativeMap getBidirectionalMap() { + return mergedClasses; + } + public Map getForwardMap() { return mergedClasses.getForwardMap(); } diff --git a/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java index a736fe93c7..8154252354 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java @@ -111,11 +111,6 @@ public boolean isAppliedLens() { return true; } - @Override - public DexType getOriginalType(DexType type) { - return renamedTypeNames.getRepresentativeKeyOrDefault(type, type); - } - @Override public Iterable getOriginalTypes(DexType type) { Set originalTypes = renamedTypeNames.getKeys(type); @@ -123,34 +118,29 @@ public Iterable getOriginalTypes(DexType type) { } @Override - public DexField getOriginalFieldSignature(DexField field) { - return originalFieldSignatures.getOrDefault(field, field); + public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition( + DexMethod method, GraphLens codeLens) { + return GraphLens.getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens); } @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - if (this == codeLens) { - return originalField; - } - return originalFieldSignatures.inverse().getOrDefault(originalField, originalField); + public DexType getPreviousClassType(DexType type) { + return renamedTypeNames.getRepresentativeKeyOrDefault(type, type); } @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - return this != applied - ? originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod) - : originalMethod; + public DexType getNextClassType(DexType previous) { + return renamedTypeNames.getOrDefault(previous, previous); } @Override - public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition( - DexMethod method, GraphLens codeLens) { - return GraphLens.getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens); + public DexField getPreviousFieldSignature(DexField field) { + return originalFieldSignatures.getOrDefault(field, field); } @Override - public DexType internalDescribeLookupClassType(DexType previous) { - return renamedTypeNames.getOrDefault(previous, previous); + public DexField getNextFieldSignature(DexField field) { + return originalFieldSignatures.inverse().getOrDefault(field, field); } @Override diff --git a/src/main/java/com/android/tools/r8/graph/lens/ClearCodeRewritingGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/ClearCodeRewritingGraphLens.java index 1635380b65..0b5348c7f9 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/ClearCodeRewritingGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/ClearCodeRewritingGraphLens.java @@ -19,18 +19,6 @@ public ClearCodeRewritingGraphLens(DexItemFactory dexItemFactory, GraphLens prev super(dexItemFactory, previousLens); } - @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - return this != codeLens ? getPrevious().getRenamedFieldSignature(originalField) : originalField; - } - - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - return this != applied - ? getPrevious().getRenamedMethodSignature(originalMethod, applied) - : originalMethod; - } - @Override public boolean isClearCodeRewritingLens() { return true; diff --git a/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java index bf57958390..ffab2e0e84 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java @@ -32,18 +32,18 @@ public boolean isContextFreeForMethods(GraphLens codeLens) { // Class lookup APIs. @Override - protected DexType internalDescribeLookupClassType(DexType previous) { - return previous; + protected DexType getNextClassType(DexType type) { + return type; } @Override - public DexType getOriginalType(DexType type) { - return getPrevious().getOriginalType(type); + public Iterable getOriginalTypes(DexType type) { + return getPrevious().getOriginalTypes(type); } @Override - public Iterable getOriginalTypes(DexType type) { - return getPrevious().getOriginalTypes(type); + public DexType getPreviousClassType(DexType type) { + return type; } // Field lookup APIs. @@ -54,16 +54,13 @@ protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previo } @Override - public DexField getOriginalFieldSignature(DexField field) { - return getPrevious().getOriginalFieldSignature(field); + public DexField getPreviousFieldSignature(DexField field) { + return field; } @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - if (this == codeLens) { - return originalField; - } - return getPrevious().getRenamedFieldSignature(originalField); + public DexField getNextFieldSignature(DexField field) { + return field; } // Method lookup APIs. @@ -84,14 +81,6 @@ public DexMethod getNextMethodSignature(DexMethod method) { return method; } - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens codeLens) { - if (this == codeLens) { - return originalMethod; - } - return getNextMethodSignature(getPrevious().getRenamedMethodSignature(originalMethod)); - } - // Prototype lookup APIs. @Override diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index 5c884ce726..7aff66159f 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java @@ -3,14 +3,12 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph.lens; -import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull; import static com.android.tools.r8.utils.collections.ThrowingSet.isThrowingSet; +import static com.google.common.base.Predicates.alwaysFalse; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexCallSite; -import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; @@ -42,13 +40,16 @@ import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Deque; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Predicate; /** * A GraphLens implements a virtual view on top of the graph, used to delay global rewrites until @@ -70,21 +71,11 @@ public abstract class GraphLens { public abstract static class Builder { - protected final MutableBidirectionalManyToOneRepresentativeMap fieldMap = - BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); protected final MutableBidirectionalManyToOneRepresentativeMap methodMap = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); - protected final Map typeMap = new IdentityHashMap<>(); protected Builder() {} - public void map(DexType from, DexType to) { - if (from == to) { - return; - } - typeMap.put(from, to); - } - public void move(DexMethod from, DexMethod to) { if (from == to) { return; @@ -92,13 +83,6 @@ public void move(DexMethod from, DexMethod to) { methodMap.put(from, to); } - public void move(DexField from, DexField to) { - if (from == to) { - return; - } - fieldMap.put(from, to); - } - public abstract GraphLens build(AppView appView); } @@ -112,41 +96,55 @@ public boolean isSyntheticFinalizationGraphLens() { return false; } - public abstract DexType getOriginalType(DexType type); + @Deprecated + public final DexType getOriginalType(DexType type) { + GraphLens appliedLens = getIdentityLens(); + return getOriginalType(type, appliedLens); + } + + public final DexType getOriginalType(DexType type, GraphLens appliedLens) { + return getOriginalReference(type, appliedLens, NonIdentityGraphLens::getPreviousClassType); + } public abstract Iterable getOriginalTypes(DexType type); - public abstract DexField getOriginalFieldSignature(DexField field); + @Deprecated + public final DexField getOriginalFieldSignature(DexField field) { + GraphLens appliedLens = getIdentityLens(); + return getOriginalFieldSignature(field, appliedLens); + } - public final DexMember getOriginalMemberSignature(DexMember member) { - return member.apply(this::getOriginalFieldSignature, this::getOriginalMethodSignature); + public final DexField getOriginalFieldSignature(DexField field, GraphLens appliedLens) { + return getOriginalReference( + field, appliedLens, NonIdentityGraphLens::getPreviousFieldSignature); } + @Deprecated public final DexMethod getOriginalMethodSignature(DexMethod method) { - return getOriginalMethodSignature(method, null); + GraphLens appliedLens = getIdentityLens(); + return getOriginalMethodSignature(method, appliedLens); } - public final DexMethod getOriginalMethodSignature(DexMethod method, GraphLens atGraphLens) { - GraphLens current = this; - DexMethod original = method; - while (current.isNonIdentityLens() && current != atGraphLens) { - NonIdentityGraphLens nonIdentityLens = current.asNonIdentityLens(); - original = nonIdentityLens.getPreviousMethodSignature(original); - current = nonIdentityLens.getPrevious(); - } - assert atGraphLens == null ? current.isIdentityLens() : (current == atGraphLens); - return original; + public final DexMethod getOriginalMethodSignature(DexMethod method, GraphLens appliedLens) { + return getOriginalReference( + method, appliedLens, NonIdentityGraphLens::getPreviousMethodSignature); } public final DexMethod getOriginalMethodSignatureForMapping(DexMethod method) { + GraphLens appliedLens = getIdentityLens(); + return getOriginalReference( + method, appliedLens, NonIdentityGraphLens::getPreviousMethodSignatureForMapping); + } + + private T getOriginalReference( + T reference, GraphLens appliedLens, BiFunction previousFn) { GraphLens current = this; - DexMethod original = method; - while (current.isNonIdentityLens()) { + T original = reference; + while (current.isNonIdentityLens() && current != appliedLens) { NonIdentityGraphLens nonIdentityLens = current.asNonIdentityLens(); - original = nonIdentityLens.getPreviousMethodSignatureForMapping(original); + original = previousFn.apply(nonIdentityLens, original); current = nonIdentityLens.getPrevious(); } - assert current.isIdentityLens(); return original; } @@ -158,11 +156,16 @@ public final DexReference getRenamedReference( method -> getRenamedMethodSignature(method, codeLens)); } + @Deprecated public final DexField getRenamedFieldSignature(DexField originalField) { - return getRenamedFieldSignature(originalField, null); + GraphLens appliedLens = getIdentityLens(); + return getRenamedFieldSignature(originalField, appliedLens); } - public abstract DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens); + public final DexField getRenamedFieldSignature(DexField originalField, GraphLens appliedLens) { + return getRenamedReference( + originalField, appliedLens, NonIdentityGraphLens::getNextFieldSignature); + } public final DexMember getRenamedMemberSignature( DexMember originalMember, GraphLens codeLens) { @@ -171,50 +174,41 @@ public final DexField getRenamedFieldSignature(DexField originalField) { : getRenamedMethodSignature(originalMember.asDexMethod(), codeLens); } + @Deprecated public final DexMethod getRenamedMethodSignature(DexMethod originalMethod) { - return getRenamedMethodSignature(originalMethod, null); + GraphLens appliedLens = getIdentityLens(); + return getRenamedMethodSignature(originalMethod, appliedLens); } - public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied); - - public DexEncodedMethod mapDexEncodedMethod( - DexEncodedMethod originalEncodedMethod, DexDefinitionSupplier definitions) { - return mapDexEncodedMethod(originalEncodedMethod, definitions, null); + public final DexMethod getRenamedMethodSignature(DexMethod method, GraphLens appliedLens) { + return getRenamedReference(method, appliedLens, NonIdentityGraphLens::getNextMethodSignature); } - public DexEncodedMethod mapDexEncodedMethod( - DexEncodedMethod originalEncodedMethod, - DexDefinitionSupplier definitions, - GraphLens applied) { - assert originalEncodedMethod != DexEncodedMethod.SENTINEL; - DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.getReference(), applied); - // Note that: - // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it - // up, since `originalEncodedMethod` may be obsolete. - // * We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be - // updated either yet. - DexClass newHolder = definitions.definitionFor(newMethod.holder); - assert newHolder != null; - DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod); - assert newEncodedMethod != null; - return newEncodedMethod; - } - - public ProgramMethod mapProgramMethod( - ProgramMethod oldMethod, DexDefinitionSupplier definitions) { - DexMethod newMethod = getRenamedMethodSignature(oldMethod.getReference()); - if (newMethod == oldMethod.getReference() && !oldMethod.getDefinition().isObsolete()) { - assert verifyIsConsistentWithLookup(oldMethod, definitions); - return oldMethod; - } - return asProgramMethodOrNull(definitions.definitionFor(newMethod)); + private T getRenamedReference( + T reference, GraphLens appliedLens, BiFunction nextFn) { + return getRenamedReference(reference, appliedLens, nextFn, alwaysFalse()); } - private static boolean verifyIsConsistentWithLookup( - ProgramMethod method, DexDefinitionSupplier definitions) { - DexClassAndMethod lookupMethod = definitions.definitionFor(method.getReference()); - assert method.getDefinition() == lookupMethod.getDefinition(); - return true; + private T getRenamedReference( + T reference, + GraphLens appliedLens, + BiFunction nextFn, + Predicate stoppingCriterion) { + GraphLens current = this; + Deque lenses = new ArrayDeque<>(); + while (current.isNonIdentityLens() && current != appliedLens) { + NonIdentityGraphLens nonIdentityLens = current.asNonIdentityLens(); + lenses.addLast(nonIdentityLens); + current = nonIdentityLens.getPrevious(); + } + while (!lenses.isEmpty()) { + NonIdentityGraphLens lens = lenses.removeLast(); + reference = nextFn.apply(lens, reference); + if (stoppingCriterion.test(reference)) { + break; + } + } + return reference; } // Predicate indicating if a rewritten reference is a simple renaming, meaning the move from one @@ -248,17 +242,24 @@ public final boolean isSimpleRenaming(DexMethod method) { public abstract String lookupPackageName(String pkg); - public DexType lookupClassType(DexType type) { - return lookupClassType(type, getIdentityLens()); + @Deprecated + public final DexType lookupClassType(DexType type) { + GraphLens appliedLens = getIdentityLens(); + return lookupClassType(type, appliedLens); } - public abstract DexType lookupClassType(DexType type, GraphLens applied); + public final DexType lookupClassType(DexType type, GraphLens appliedLens) { + return getRenamedReference( + type, appliedLens, NonIdentityGraphLens::getNextClassType, DexType::isPrimitiveType); + } + @Deprecated public DexType lookupType(DexType type) { - return lookupType(type, getIdentityLens()); + GraphLens appliedLens = getIdentityLens(); + return lookupType(type, appliedLens); } - public abstract DexType lookupType(DexType type, GraphLens applied); + public abstract DexType lookupType(DexType type, GraphLens appliedLens); public final MethodLookupResult lookupInvokeDirect(DexMethod method, ProgramMethod context) { return lookupMethod(method, context.getReference(), InvokeType.DIRECT); diff --git a/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java index 02c6a27a11..e72f128fde 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java @@ -36,31 +36,11 @@ public boolean isNonIdentityLens() { return false; } - @Override - public DexType getOriginalType(DexType type) { - return type; - } - @Override public Iterable getOriginalTypes(DexType type) { return IterableUtils.singleton(type); } - @Override - public DexField getOriginalFieldSignature(DexField field) { - return field; - } - - @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - return originalField; - } - - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - return originalMethod; - } - @Override public String lookupPackageName(String pkg) { return pkg; @@ -71,12 +51,6 @@ public DexType lookupType(DexType type, GraphLens applied) { return type; } - @Override - public DexType lookupClassType(DexType type, GraphLens applied) { - assert type.isClassType(); - return type; - } - @Override public MethodLookupResult lookupMethod( DexMethod method, DexMethod context, InvokeType type, GraphLens codeLens) { diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java index 694425b561..93c76a06c6 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java @@ -16,14 +16,13 @@ import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap; import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap; -import java.util.Collections; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** - * GraphLens implementation with a parent lens using a simple mapping for type, method and field - * mapping. + * GraphLens implementation with a parent lens where the mapping of types, methods and fields can be + * one-to-one, one-to-many, or many-to-one. * *

Subclasses can override the lookup methods. * @@ -37,11 +36,12 @@ public class NestedGraphLens extends DefaultNonIdentityGraphLens { new EmptyBidirectionalOneToOneMap<>(); protected static final EmptyBidirectionalOneToOneMap EMPTY_METHOD_MAP = new EmptyBidirectionalOneToOneMap<>(); - protected static final Map EMPTY_TYPE_MAP = Collections.emptyMap(); + protected static final EmptyBidirectionalOneToOneMap EMPTY_TYPE_MAP = + new EmptyBidirectionalOneToOneMap<>(); protected final BidirectionalManyToOneRepresentativeMap fieldMap; protected final Function methodMap; - protected final Map typeMap; + protected final BidirectionalManyToManyRepresentativeMap typeMap; // Map that stores the new signature of methods that have been affected by class merging, unused // argument removal, repackaging, synthetic finalization, etc. This is needed to generate a @@ -81,7 +81,7 @@ public NestedGraphLens( AppView appView, BidirectionalManyToOneRepresentativeMap fieldMap, BidirectionalManyToOneRepresentativeMap methodMap, - Map typeMap) { + BidirectionalManyToManyRepresentativeMap typeMap) { this(appView, fieldMap, methodMap.getForwardMap(), typeMap, methodMap); } @@ -89,7 +89,7 @@ public NestedGraphLens( AppView appView, BidirectionalManyToOneRepresentativeMap fieldMap, Map methodMap, - Map typeMap, + BidirectionalManyToManyRepresentativeMap typeMap, BidirectionalManyToManyRepresentativeMap newMethodSignatures) { this(appView, fieldMap, methodMap::get, typeMap, newMethodSignatures); assert !typeMap.isEmpty() @@ -102,7 +102,7 @@ public NestedGraphLens( AppView appView, BidirectionalManyToOneRepresentativeMap fieldMap, Function methodMap, - Map typeMap, + BidirectionalManyToManyRepresentativeMap typeMap, BidirectionalManyToManyRepresentativeMap newMethodSignatures) { super(appView); this.fieldMap = fieldMap; @@ -111,51 +111,23 @@ public NestedGraphLens( this.newMethodSignatures = newMethodSignatures; } - protected DexType internalGetOriginalType(DexType previous) { - return previous; - } - - protected Iterable internalGetOriginalTypes(DexType previous) { - return IterableUtils.singleton(internalGetOriginalType(previous)); - } - @Override - public DexType getOriginalType(DexType type) { - return getPrevious().getOriginalType(internalGetOriginalType(type)); + public DexType getPreviousClassType(DexType type) { + return typeMap.getRepresentativeKeyOrDefault(type, type); } @Override - public Iterable getOriginalTypes(DexType type) { - return IterableUtils.flatMap(internalGetOriginalTypes(type), getPrevious()::getOriginalTypes); + protected DexType getNextClassType(DexType type) { + return typeMap.getRepresentativeValueOrDefault(type, type); } - @Override - public DexField getOriginalFieldSignature(DexField field) { - DexField originalField = fieldMap.getRepresentativeKeyOrDefault(field, field); - return getPrevious().getOriginalFieldSignature(originalField); + protected Iterable internalGetOriginalTypes(DexType type) { + return IterableUtils.singleton(getPreviousClassType(type)); } @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - if (this == codeLens) { - return originalField; - } - DexField renamedField = getPrevious().getRenamedFieldSignature(originalField, codeLens); - return internalGetNextFieldSignature(renamedField); - } - - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - if (this == applied) { - return originalMethod; - } - DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied); - return getNextMethodSignature(renamedMethod); - } - - @Override - protected DexType internalDescribeLookupClassType(DexType previous) { - return typeMap.getOrDefault(previous, previous); + public Iterable getOriginalTypes(DexType type) { + return IterableUtils.flatMap(internalGetOriginalTypes(type), getPrevious()::getOriginalTypes); } @Override @@ -167,14 +139,12 @@ protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previo previous.getReference() == previous.getReboundReference() ? rewrittenReboundReference : rewrittenReboundReference.withHolder( - internalDescribeLookupClassType(previous.getReference().getHolderType()), - dexItemFactory()); + getNextClassType(previous.getReference().getHolderType()), dexItemFactory()); return FieldLookupResult.builder(this) .setReboundReference(rewrittenReboundReference) .setReference(rewrittenNonReboundReference) - .setReadCastType(previous.getRewrittenReadCastType(this::internalDescribeLookupClassType)) - .setWriteCastType( - previous.getRewrittenWriteCastType(this::internalDescribeLookupClassType)) + .setReadCastType(previous.getRewrittenReadCastType(this::getNextClassType)) + .setWriteCastType(previous.getRewrittenWriteCastType(this::getNextClassType)) .build(); } else { // TODO(b/168282032): We should always have the rebound reference, so this should become @@ -182,9 +152,8 @@ protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previo DexField rewrittenReference = previous.getRewrittenReference(fieldMap); return FieldLookupResult.builder(this) .setReference(rewrittenReference) - .setReadCastType(previous.getRewrittenReadCastType(this::internalDescribeLookupClassType)) - .setWriteCastType( - previous.getRewrittenWriteCastType(this::internalDescribeLookupClassType)) + .setReadCastType(previous.getRewrittenReadCastType(this::getNextClassType)) + .setWriteCastType(previous.getRewrittenWriteCastType(this::getNextClassType)) .build(); } } @@ -201,8 +170,7 @@ public MethodLookupResult internalDescribeLookupMethod( ? rewrittenReboundReference : // This assumes that the holder will always be moved in lock-step with the method! rewrittenReboundReference.withHolder( - internalDescribeLookupClassType(previous.getReference().getHolderType()), - dexItemFactory()); + getNextClassType(previous.getReference().getHolderType()), dexItemFactory()); return MethodLookupResult.builder(this) .setReference(rewrittenReference) .setReboundReference(rewrittenReboundReference) @@ -253,7 +221,13 @@ protected RewrittenPrototypeDescription internalDescribePrototypeChanges( return prototypeChanges; } - protected DexField internalGetNextFieldSignature(DexField field) { + @Override + public DexField getPreviousFieldSignature(DexField field) { + return fieldMap.getRepresentativeKeyOrDefault(field, field); + } + + @Override + public DexField getNextFieldSignature(DexField field) { return fieldMap.getOrDefault(field, field); } diff --git a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java index ccbacc3e64..7680134a42 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java @@ -98,33 +98,22 @@ public String lookupPackageName(String pkg) { } @Override - public final DexType lookupType(DexType type, GraphLens applied) { - if (this == applied) { - return type; - } - if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) { - return type; + public final DexType lookupType(DexType type, GraphLens appliedLens) { + if (type.isClassType()) { + return lookupClassType(type, appliedLens); } if (type.isArrayType()) { DexType result = arrayTypeCache.get(type); if (result == null) { DexType baseType = type.toBaseType(dexItemFactory); - DexType newType = lookupType(baseType); + DexType newType = lookupType(baseType, appliedLens); result = baseType == newType ? type : type.replaceBaseType(newType, dexItemFactory); arrayTypeCache.put(type, result); } return result; } - return lookupClassType(type); - } - - @Override - public final DexType lookupClassType(DexType type, GraphLens applied) { - assert type.isClassType() : "Expected class type, but was `" + type.toSourceString() + "`"; - if (this == applied) { - return type; - } - return internalDescribeLookupClassType(getPrevious().lookupClassType(type)); + assert type.isNullValueType() || type.isPrimitiveType() || type.isVoidType(); + return type; } @Override @@ -169,10 +158,14 @@ protected MethodLookupResult internalLookupMethod( protected abstract MethodLookupResult internalDescribeLookupMethod( MethodLookupResult previous, DexMethod context, GraphLens codeLens); - protected abstract DexType internalDescribeLookupClassType(DexType previous); + protected abstract DexType getNextClassType(DexType type); + + public abstract DexField getPreviousFieldSignature(DexField field); public abstract DexMethod getPreviousMethodSignature(DexMethod method); + public abstract DexType getPreviousClassType(DexType type); + /*** * The previous mapping for a method often coincides with the previous method signature, but it * may not, for example for bridges inserted in vertically merged classes where the original @@ -182,6 +175,8 @@ public DexMethod getPreviousMethodSignatureForMapping(DexMethod method) { return getPreviousMethodSignature(method); } + public abstract DexField getNextFieldSignature(DexField field); + public abstract DexMethod getNextMethodSignature(DexMethod method); @Override diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java index cd2586ecde..b8f049d1e8 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java @@ -42,7 +42,7 @@ private HorizontalClassMergerGraphLens( BidirectionalManyToOneRepresentativeMap fieldMap, Map methodMap, BidirectionalManyToOneRepresentativeMap newMethodSignatures) { - super(appView, fieldMap, methodMap, mergedClasses.getForwardMap(), newMethodSignatures); + super(appView, fieldMap, methodMap, mergedClasses.getBidirectionalMap(), newMethodSignatures); this.methodExtraParameters = methodExtraParameters; this.mergedClasses = mergedClasses; } @@ -52,6 +52,11 @@ DexMethod getNextMethodToInvoke(DexMethod method) { return nextMethod != null ? nextMethod : method; } + @Override + public DexType getPreviousClassType(DexType type) { + return type; + } + @Override public boolean isHorizontalClassMergerGraphLens() { return true; @@ -94,7 +99,7 @@ protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previo lookup.getReference().getType() != previous.getReference().getType() ? lookupType(previous.getReference().getType()) : null) - .setWriteCastType(previous.getRewrittenWriteCastType(this::internalDescribeLookupClassType)) + .setWriteCastType(previous.getRewrittenWriteCastType(this::getNextClassType)) .build(); } diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java index 9caf1a97fb..11313e4b38 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java @@ -8,19 +8,20 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.classmerging.MergedClasses; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap; -import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap; -import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; public class HorizontallyMergedClasses implements MergedClasses { - private final BidirectionalManyToOneMap mergedClasses; + private final BidirectionalManyToOneRepresentativeMap mergedClasses; - public HorizontallyMergedClasses(BidirectionalManyToOneMap mergedClasses) { + public HorizontallyMergedClasses( + BidirectionalManyToOneRepresentativeMap mergedClasses) { this.mergedClasses = mergedClasses; } @@ -89,6 +90,10 @@ public boolean hasBeenMergedOrIsMergeTarget(DexType type) { return this.hasBeenMergedIntoDifferentType(type) || isMergeTarget(type); } + BidirectionalManyToOneRepresentativeMap getBidirectionalMap() { + return mergedClasses; + } + Map getForwardMap() { return mergedClasses.getForwardMap(); } @@ -106,8 +111,8 @@ public boolean verifyAllSourcesPruned(AppView appView) { public static class Builder { - private final MutableBidirectionalManyToOneMap mergedClasses = - BidirectionalManyToOneHashMap.newIdentityHashMap(); + private final MutableBidirectionalManyToOneRepresentativeMap mergedClasses = + BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); void add(DexType source, DexType target) { assert !mergedClasses.containsKey(source); diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java index 422dc725ad..7ac67130b4 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java @@ -17,7 +17,6 @@ import com.android.tools.r8.graph.Code; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexProto; @@ -429,11 +428,9 @@ public static class InterfaceProcessorNestedGraphLens extends NestedGraphLens { public InterfaceProcessorNestedGraphLens( AppView appView, - BidirectionalManyToOneRepresentativeMap fieldMap, BidirectionalManyToOneRepresentativeMap methodMap, - Map typeMap, BidirectionalOneToOneMap extraNewMethodSignatures) { - super(appView, fieldMap, methodMap, typeMap); + super(appView, NestedGraphLens.EMPTY_FIELD_MAP, methodMap, NestedGraphLens.EMPTY_TYPE_MAP); this.extraNewMethodSignatures = extraNewMethodSignatures; } @@ -474,11 +471,10 @@ public void recordCodeMovedToCompanionClass(DexMethod from, DexMethod to) { @Override public InterfaceProcessorNestedGraphLens build(AppView appView) { - if (fieldMap.isEmpty() && methodMap.isEmpty() && extraNewMethodSignatures.isEmpty()) { + if (methodMap.isEmpty() && extraNewMethodSignatures.isEmpty()) { return null; } - return new InterfaceProcessorNestedGraphLens( - appView, fieldMap, methodMap, typeMap, extraNewMethodSignatures); + return new InterfaceProcessorNestedGraphLens(appView, methodMap, extraNewMethodSignatures); } } } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java index 53f7f63817..dd803f91b2 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java @@ -27,10 +27,13 @@ import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap; import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeMap; import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.MutableBidirectionalOneToManyRepresentativeMap; import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap; import com.google.common.collect.ImmutableMap; @@ -51,7 +54,7 @@ public class EnumUnboxingLens extends NestedGraphLens { AppView appView, BidirectionalOneToOneMap fieldMap, BidirectionalOneToManyRepresentativeMap renamedSignatures, - Map typeMap, + BidirectionalManyToOneRepresentativeMap typeMap, Map methodMap, Map prototypeChangesPerMethod, Set dispatchMethods) { @@ -232,7 +235,8 @@ static class Builder { private final DexItemFactory dexItemFactory; private final AbstractValueFactory abstractValueFactory; - private final Map typeMap = new IdentityHashMap<>(); + private final MutableBidirectionalManyToOneRepresentativeMap typeMap = + BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); private final MutableBidirectionalOneToOneMap newFieldSignatures = new BidirectionalOneToOneHashMap<>(); private final MutableBidirectionalOneToManyRepresentativeMap diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java index 4441690613..553962d7ec 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java @@ -160,7 +160,7 @@ public void fixup( @Override public DexField fixupFieldReference(DexField field) { - return graphLens.internalGetNextFieldSignature(field); + return graphLens.getNextFieldSignature(field); } @Override diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java index 42213aa3c3..af130e81de 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java @@ -85,21 +85,6 @@ protected RewrittenPrototypeDescription internalDescribePrototypeChanges( return newPrototypeChanges; } - @Override - public DexMethod getPreviousMethodSignature(DexMethod method) { - return super.getPreviousMethodSignature(method); - } - - @Override - public DexField internalGetNextFieldSignature(DexField field) { - return super.internalGetNextFieldSignature(field); - } - - @Override - public DexMethod getNextMethodSignature(DexMethod method) { - return super.getNextMethodSignature(method); - } - @Override protected InvokeType mapInvocationType( DexMethod newMethod, DexMethod originalMethod, InvokeType type) { diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java index acdb13b42e..aa0bde9797 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java @@ -230,7 +230,7 @@ private void registerFieldAccess(DexField field) { ProgramField resolvedField = resolutionResult.getSingleProgramField(); DexField rewrittenFieldReference = - graphLens.internalGetNextFieldSignature(resolvedField.getReference()); + graphLens.getNextFieldSignature(resolvedField.getReference()); if (rewrittenFieldReference != resolvedField.getReference()) { markAffected(); } diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java index 69f924853c..694096a2e4 100644 --- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java +++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java @@ -7,7 +7,6 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.lens.DefaultNonIdentityGraphLens; -import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; import java.util.Set; @@ -23,12 +22,6 @@ public BridgeHoistingLens( this.bridgeToHoistedBridgeMap = bridgeToHoistedBridgeMap; } - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied); - return getNextMethodSignature(renamedMethod); - } - @Override public DexMethod getPreviousMethodSignature(DexMethod method) { Set bridges = bridgeToHoistedBridgeMap.getKeys(method); diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java index 3307010803..9de23a8d48 100644 --- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java +++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java @@ -33,13 +33,8 @@ public RedundantBridgeRemovalLens( // Methods. @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - if (this == applied) { - return originalMethod; - } - DexMethod previousMethodSignature = - getPrevious().getRenamedMethodSignature(originalMethod, applied); - return methodMap.getOrDefault(previousMethodSignature, previousMethodSignature); + public DexMethod getNextMethodSignature(DexMethod method) { + return methodMap.getOrDefault(method, method); } @Override diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java index 7a462ed3b5..7abe044c92 100644 --- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java +++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java @@ -16,20 +16,18 @@ import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; import java.util.Map; public class RepackagingLens extends NestedGraphLens { - private final BiMap newTypes; + private final BidirectionalOneToOneMap newTypes; private final Map packageRenamings; private RepackagingLens( AppView appView, BidirectionalOneToOneMap newFieldSignatures, BidirectionalOneToOneMap newMethodSignatures, - BiMap newTypes, + BidirectionalOneToOneMap newTypes, Map packageRenamings) { super(appView, newFieldSignatures, newMethodSignatures, newTypes); this.newTypes = newTypes; @@ -41,12 +39,6 @@ public String lookupPackageName(String pkg) { return packageRenamings.getOrDefault(getPrevious().lookupPackageName(pkg), pkg); } - @Override - public DexType getOriginalType(DexType type) { - DexType previous = newTypes.inverse().getOrDefault(type, type); - return getPrevious().getOriginalType(previous); - } - @Override public boolean isSimpleRenaming(T from, T to) { if (from == to) { @@ -87,7 +79,8 @@ private boolean isSimpleTypeRenamingOrEqual(DexMember from, DexMember newTypes = HashBiMap.create(); + protected final MutableBidirectionalOneToOneMap newTypes = + new BidirectionalOneToOneHashMap<>(); protected final MutableBidirectionalOneToOneMap newFieldSignatures = new BidirectionalOneToOneHashMap<>(); protected final MutableBidirectionalOneToOneMap newMethodSignatures = diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index eda8554d02..a7106fc35c 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java @@ -83,7 +83,9 @@ import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.Iterables; @@ -166,8 +168,8 @@ private enum Rename { private final Set mergeCandidates = new LinkedHashSet<>(); // Map from source class to target class. - private final MutableBidirectionalManyToOneMap mergedClasses = - BidirectionalManyToOneHashMap.newIdentityHashMap(); + private final MutableBidirectionalManyToOneRepresentativeMap mergedClasses = + BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); private final MutableBidirectionalManyToOneMap mergedInterfaces = BidirectionalManyToOneHashMap.newIdentityHashMap(); @@ -1883,34 +1885,29 @@ public SingleTypeMapperGraphLens(DexType source, DexProgramClass target) { this.target = target; } - @Override - public DexType getOriginalType(DexType type) { - throw new Unreachable(); - } - @Override public Iterable getOriginalTypes(DexType type) { throw new Unreachable(); } @Override - public DexField getOriginalFieldSignature(DexField field) { + public DexType getPreviousClassType(DexType type) { throw new Unreachable(); } @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - throw new Unreachable(); + public final DexType getNextClassType(DexType type) { + return type == source ? target.type : mergedClasses.getOrDefault(type, type); } @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { + public DexField getPreviousFieldSignature(DexField field) { throw new Unreachable(); } @Override - public final DexType internalDescribeLookupClassType(DexType previous) { - return previous == source ? target.type : mergedClasses.getOrDefault(previous, previous); + public DexField getNextFieldSignature(DexField field) { + throw new Unreachable(); } @Override diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java index 3aeaad0423..0f89af126a 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java @@ -83,7 +83,7 @@ private VerticalClassMergerGraphLens( BidirectionalManyToOneRepresentativeMap newMethodSignatures, Map originalMethodSignaturesForBridges, Map prototypeChanges) { - super(appView, fieldMap, methodMap, mergedClasses.getForwardMap(), newMethodSignatures); + super(appView, fieldMap, methodMap, mergedClasses.getBidirectionalMap(), newMethodSignatures); this.appView = appView; this.mergedClasses = mergedClasses; this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps; @@ -97,6 +97,11 @@ public boolean isVerticalClassMergerLens() { return true; } + @Override + public DexType getPreviousClassType(DexType type) { + return type; + } + @Override protected Iterable internalGetOriginalTypes(DexType previous) { Collection originalTypes = mergedClasses.getSourcesFor(previous); diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java index 9ed366e49c..462895f934 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java @@ -90,10 +90,15 @@ private SyntheticFinalizationGraphLens( AppView appView, BidirectionalManyToOneRepresentativeMap fieldMap, BidirectionalManyToOneRepresentativeMap methodMap, - Map typeMap) { + BidirectionalManyToOneRepresentativeMap typeMap) { super(appView, fieldMap, methodMap, typeMap); } + @Override + public DexType getPreviousClassType(DexType type) { + return type; + } + @Override public boolean isSyntheticFinalizationGraphLens() { return true; @@ -111,7 +116,8 @@ private static class Builder { BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); private final MutableBidirectionalManyToOneRepresentativeMap methodMap = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); - private final Map typeMap = new IdentityHashMap<>(); + private final MutableBidirectionalManyToOneRepresentativeMap typeMap = + BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); boolean isEmpty() { if (typeMap.isEmpty()) { diff --git a/src/main/java/com/android/tools/r8/utils/LensUtils.java b/src/main/java/com/android/tools/r8/utils/LensUtils.java index 4097730772..b0eae54b6f 100644 --- a/src/main/java/com/android/tools/r8/utils/LensUtils.java +++ b/src/main/java/com/android/tools/r8/utils/LensUtils.java @@ -99,9 +99,10 @@ public static Map mutableRewriteMap( public static Set rewrittenWithRenamedSignature( Set methods, DexDefinitionSupplier definitions, GraphLens lens) { + GraphLens appliedLens = GraphLens.getIdentityLens(); Set result = Sets.newIdentityHashSet(); for (DexEncodedMethod method : methods) { - result.add(lens.mapDexEncodedMethod(method, definitions)); + result.add(method.rewrittenWithLens(lens, appliedLens, definitions)); } return result; } diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java index 296111114b..aca734e45c 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java @@ -26,6 +26,10 @@ public BidirectionalOneToOneHashMap(BiMap backing) { this.backing = backing; } + public BidirectionalOneToOneHashMap(Map backing) { + this(HashBiMap.create(backing)); + } + @Override public void clear() { backing.clear(); diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java index 0ac1e7cc89..a53de2fc6a 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java @@ -87,10 +87,11 @@ public boolean createAndAdd(DexProgramClass clazz, DexEncodedMethod definition) } public ProgramMethodSet rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) { + GraphLens appliedLens = GraphLens.getIdentityLens(); List elementsToRemove = null; ProgramMethodSet rewrittenMethods = null; for (ProgramMethod method : this) { - ProgramMethod rewrittenMethod = lens.mapProgramMethod(method, definitions); + ProgramMethod rewrittenMethod = method.rewrittenWithLens(lens, appliedLens, definitions); if (rewrittenMethod == null) { assert lens.isEnumUnboxerLens(); // If everything has been unchanged up until now, then record that we should remove this diff --git a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java index aa24dde684..79f582ba27 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java @@ -58,8 +58,12 @@ Map createBacking(int capacity) { @Override public SortedProgramMethodSet rewrittenWithLens( DexDefinitionSupplier definitions, GraphLens lens) { + GraphLens appliedLens = GraphLens.getIdentityLens(); return create( - consumer -> forEach(method -> consumer.accept(lens.mapProgramMethod(method, definitions)))); + consumer -> + forEach( + method -> + consumer.accept(method.rewrittenWithLens(lens, appliedLens, definitions)))); } @Override From ae686a5c06104db4d8193acaad24b79dd153dbb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 15 Jun 2023 13:22:15 +0200 Subject: [PATCH 115/153] Split MoveResultRewriter off CodeRewriter Bug: b/284304606 Change-Id: I2157b612e60dd52233c36305263d4156457bf269 --- .../tools/r8/ir/conversion/IRConverter.java | 11 +- .../passes/ArrayConstructionSimplifier.java | 4 +- .../ir/conversion/passes/BinopRewriter.java | 4 +- .../conversion/passes/CodeRewriterPass.java | 24 +-- .../CommonSubexpressionElimination.java | 4 +- .../passes/DexConstantOptimizer.java | 4 +- .../passes/KnownArrayLengthRewriter.java | 4 +- .../conversion/passes/MoveResultRewriter.java | 145 ++++++++++++++++++ .../passes/NaturalIntLoopRemover.java | 4 +- ...ParentConstructorHoistingCodeRewriter.java | 4 +- .../r8/ir/conversion/passes/SplitBranch.java | 14 +- .../TrivialCheckCastAndInstanceOfRemover.java | 4 +- .../passes/TrivialGotosCollapser.java | 4 +- .../passes/result/CodeRewriterResult.java | 40 +++++ .../tools/r8/ir/optimize/CodeRewriter.java | 113 -------------- .../tools/r8/ir/optimize/DeadCodeRemover.java | 13 +- .../ir/optimize/enums/EnumValueOptimizer.java | 4 +- .../r8/ir/optimize/outliner/OutlinerImpl.java | 5 +- .../EnqueuerDeferredTracingRewriter.java | 2 +- 19 files changed, 250 insertions(+), 157 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 2a2813f685..6007ea9f97 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -33,6 +33,7 @@ import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer; import com.android.tools.r8.ir.conversion.passes.KnownArrayLengthRewriter; +import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.ir.conversion.passes.NaturalIntLoopRemover; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; @@ -182,7 +183,7 @@ public IRConverter(AppView appView) { this.classInitializerDefaultsOptimization = new ClassInitializerDefaultsOptimization(appView, this); this.stringOptimizer = new StringOptimizer(appView); - this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter); + this.deadCodeRemover = new DeadCodeRemover(appView); this.assertionsRewriter = new AssertionsRewriter(appView); this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView); this.neverMerge = @@ -383,7 +384,7 @@ public void processSimpleSynthesizeMethods( private void processAndFinalizeSimpleSynthesizedMethod(ProgramMethod method) { IRCode code = method.buildIR(appView); assert code != null; - codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, Timing.empty()); removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty()); } @@ -750,9 +751,7 @@ Timing optimize( } commonSubexpressionElimination.run(code, timing); new ArrayConstructionSimplifier(appView).run(code, timing); - timing.begin("Rewrite move result"); - codeRewriter.rewriteMoveResult(code); - timing.end(); + new MoveResultRewriter(appView).run(code, timing); if (options.enableStringConcatenationOptimization && !isDebugMode) { timing.begin("Rewrite string concat"); StringBuilderAppendOptimizer.run(appView, code); @@ -930,7 +929,7 @@ Timing optimize( assert code.isConsistentSSA(appView); // TODO(b/214496607): Remove when dynamic types are safe w.r.t. interface assignment rules. - codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, timing); } // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL. diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java index 63e6b3690e..61cc2c1737 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java @@ -20,6 +20,7 @@ import com.android.tools.r8.ir.code.NewArrayEmpty; import com.android.tools.r8.ir.code.NewArrayFilledData; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions; import com.android.tools.r8.utils.SetUtils; @@ -92,12 +93,13 @@ protected String getTimingId() { } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { WorkList worklist = WorkList.newIdentityWorkList(code.blocks); while (worklist.hasNext()) { BasicBlock block = worklist.next(); simplifyArrayConstructionBlock(block, worklist, code, appView.options()); } + return CodeRewriterResult.NONE; } @Override diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java index 24fad2eea9..f684ff537e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java @@ -27,6 +27,7 @@ import com.android.tools.r8.ir.code.Ushr; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.Xor; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.ImmutableMap; import java.util.Map; @@ -248,7 +249,7 @@ protected boolean shouldRewriteCode(IRCode code) { } @Override - public void rewriteCode(IRCode code) { + public CodeRewriterResult rewriteCode(IRCode code) { InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) { Instruction next = iterator.next(); @@ -268,6 +269,7 @@ public void rewriteCode(IRCode code) { code.removeAllDeadAndTrivialPhis(); code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } private void successiveSimplification( diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java index 94e30d3df7..aba955c9b9 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java @@ -11,6 +11,7 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Timing; @@ -31,38 +32,43 @@ protected AppView appView() { return (AppView) appView; } - public final void run( + public final CodeRewriterResult run( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext, Timing timing) { - timing.time(getTimingId(), () -> run(code, methodProcessor, methodProcessingContext)); + return timing.time(getTimingId(), () -> run(code, methodProcessor, methodProcessingContext)); } - public final void run(IRCode code, Timing timing) { - timing.time(getTimingId(), () -> run(code, null, null)); + public final CodeRewriterResult run(IRCode code, Timing timing) { + return timing.time(getTimingId(), () -> run(code, null, null)); } - private void run( + private CodeRewriterResult run( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { if (shouldRewriteCode(code)) { - rewriteCode(code, methodProcessor, methodProcessingContext); + return rewriteCode(code, methodProcessor, methodProcessingContext); } + return noChange(); + } + + protected CodeRewriterResult noChange() { + return CodeRewriterResult.NO_CHANGE; } protected abstract String getTimingId(); - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { throw new Unreachable("Should Override or use overload"); } - protected void rewriteCode( + protected CodeRewriterResult rewriteCode( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { - rewriteCode(code); + return rewriteCode(code); } protected abstract boolean shouldRewriteCode(IRCode code); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java index 5d88dedede..7057abc900 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java @@ -15,6 +15,7 @@ import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.InternalOptions; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; @@ -39,7 +40,7 @@ protected boolean shouldRewriteCode(IRCode code) { } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { int noCandidate = code.reserveMarkingColor(); if (hasCSECandidate(code, noCandidate)) { final ListMultimap, Value> instructionToValue = @@ -79,6 +80,7 @@ && shareCatchHandlers(instruction, candidate.definition)) { code.returnMarkingColor(noCandidate); code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } private static class CSEExpressionEquivalence extends Equivalence { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java index c6e07ed844..bcfd88c483 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java @@ -31,6 +31,7 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.ConstantCanonicalizer; import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.Iterables; @@ -63,9 +64,10 @@ protected String getTimingId() { } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { useDedicatedConstantForLitInstruction(code); shortenLiveRanges(code, constantCanonicalizer); + return CodeRewriterResult.NONE; } @Override diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java index 9ae7aabf0b..291f8b991f 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java @@ -13,6 +13,7 @@ import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import java.util.Set; public class KnownArrayLengthRewriter extends CodeRewriterPass { @@ -32,7 +33,7 @@ protected boolean shouldRewriteCode(IRCode code) { } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) { Instruction current = iterator.next(); @@ -78,5 +79,6 @@ protected void rewriteCode(IRCode code) { } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java new file mode 100644 index 0000000000..4481aa0d7f --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java @@ -0,0 +1,145 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; + +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; +import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeMethod; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; +import com.android.tools.r8.ir.optimize.AssumeRemover; +import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.ListIterator; +import java.util.Set; + +public class MoveResultRewriter extends CodeRewriterPass { + + public MoveResultRewriter(AppView appView) { + super(appView); + } + + @Override + protected String getTimingId() { + return "MoveResultRewriter"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return options.isGeneratingDex() && code.metadata().mayHaveInvokeMethod(); + } + + // Replace result uses for methods where something is known about what is returned. + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + AssumeRemover assumeRemover = new AssumeRemover(appView, code); + boolean changed = false; + boolean mayHaveRemovedTrivialPhi = false; + Set blocksToBeRemoved = Sets.newIdentityHashSet(); + ListIterator blockIterator = code.listIterator(); + while (blockIterator.hasNext()) { + BasicBlock block = blockIterator.next(); + if (blocksToBeRemoved.contains(block)) { + continue; + } + + InstructionListIterator iterator = block.listIterator(code); + while (iterator.hasNext()) { + InvokeMethod invoke = iterator.next().asInvokeMethod(); + if (invoke == null || !invoke.hasOutValue() || invoke.outValue().hasLocalInfo()) { + continue; + } + + // Check if the invoked method is known to return one of its arguments. + DexClassAndMethod target = invoke.lookupSingleTarget(appView, code.context()); + if (target == null) { + continue; + } + + MethodOptimizationInfo optimizationInfo = target.getDefinition().getOptimizationInfo(); + if (!optimizationInfo.returnsArgument()) { + continue; + } + + int argumentIndex = optimizationInfo.getReturnedArgument(); + // Replace the out value of the invoke with the argument and ignore the out value. + if (argumentIndex < 0 || !checkArgumentType(invoke, argumentIndex)) { + continue; + } + + Value argument = invoke.arguments().get(argumentIndex); + Value outValue = invoke.outValue(); + assert outValue.verifyCompatible(argument.outType()); + + // Make sure that we are only narrowing information here. Note, in cases where we cannot + // find the definition of types, computing lessThanOrEqual will return false unless it is + // object. + if (!argument.getType().lessThanOrEqual(outValue.getType(), appView)) { + continue; + } + + Set affectedValues = + argument.getType().equals(outValue.getType()) + ? Collections.emptySet() + : outValue.affectedValues(); + + assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue); + mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0; + outValue.replaceUsers(argument); + invoke.setOutValue(null); + changed = true; + + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } + } + } + assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); + Set affectedValues = Sets.newIdentityHashSet(); + if (!blocksToBeRemoved.isEmpty()) { + code.removeBlocks(blocksToBeRemoved); + code.removeAllDeadAndTrivialPhis(affectedValues); + assert code.getUnreachableBlocks().isEmpty(); + } else if (mayHaveRemovedTrivialPhi || assumeRemover.mayHaveIntroducedTrivialPhi()) { + code.removeAllDeadAndTrivialPhis(affectedValues); + } + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } + assert code.isConsistentSSA(appView); + return CodeRewriterResult.hasChanged(changed); + } + + private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) { + // TODO(sgjesse): Insert cast if required. + TypeElement returnType = + TypeElement.fromDexType(invoke.getInvokedMethod().proto.returnType, maybeNull(), appView); + TypeElement argumentType = + TypeElement.fromDexType(getArgumentType(invoke, argumentIndex), maybeNull(), appView); + return appView.enableWholeProgramOptimizations() + ? argumentType.lessThanOrEqual(returnType, appView) + : argumentType.equals(returnType); + } + + private DexType getArgumentType(InvokeMethod invoke, int argumentIndex) { + if (invoke.isInvokeStatic()) { + return invoke.getInvokedMethod().proto.parameters.values[argumentIndex]; + } + if (argumentIndex == 0) { + return invoke.getInvokedMethod().holder; + } + return invoke.getInvokedMethod().proto.parameters.values[argumentIndex - 1]; + } +} diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java index be7840ffc0..2238b82dd4 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java @@ -15,6 +15,7 @@ import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Sub; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; import java.util.Set; @@ -40,7 +41,7 @@ protected String getTimingId() { } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { boolean loopRemoved = false; for (BasicBlock comparisonBlockCandidate : code.blocks) { if (isComparisonBlock(comparisonBlockCandidate)) { @@ -52,6 +53,7 @@ protected void rewriteCode(IRCode code) { code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } + return CodeRewriterResult.NONE; } @Override diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java index 4f96ce7335..6a7c0408a3 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java @@ -14,6 +14,7 @@ import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.shaking.KeepMethodInfo; import com.android.tools.r8.utils.CollectionUtils; import com.android.tools.r8.utils.IterableUtils; @@ -48,10 +49,11 @@ protected String getTimingId() { } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { for (InvokeDirect invoke : getOrComputeSideEffectFreeConstructorCalls(code)) { hoistSideEffectFreeConstructorCall(code, invoke); } + return CodeRewriterResult.NONE; } private void hoistSideEffectFreeConstructorCall(IRCode code, InvokeDirect invoke) { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java index fbb3afd48c..e4bac3e518 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java @@ -14,6 +14,7 @@ import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; @@ -43,24 +44,22 @@ protected boolean shouldRewriteCode(IRCode code) { } /** - * Simplify Boolean branches for example: - * boolean b = i == j; if (b) { ... } else { ... } + * Simplify Boolean branches for example: boolean b = i == j; if (b) { ... } else { ... } * ends up first creating a branch for the boolean b, then a second branch on b. D8/R8 - * rewrites to: - * if (i == j) { ... } else { ... } + * rewrites to: if (i == j) { ... } else { ... } * More complex control flow are also supported to some extent, including cases where the * input of the second branch comes from a set of dependent phis, and a subset of the inputs are * known boolean values. */ @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { List candidates = computeCandidates(code); if (candidates.isEmpty()) { - return; + return CodeRewriterResult.NONE; } Map newTargets = findGotosToRetarget(candidates); if (newTargets.isEmpty()) { - return; + return CodeRewriterResult.NONE; } retargetGotos(newTargets); Set affectedValues = Sets.newIdentityHashSet(); @@ -74,6 +73,7 @@ protected void rewriteCode(IRCode code) { } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } private void retargetGotos(Map newTargets) { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java index cbce46f391..77104d518c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java @@ -28,6 +28,7 @@ import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; @@ -54,7 +55,7 @@ protected boolean shouldRewriteCode(IRCode code) { } @Override - protected void rewriteCode( + protected CodeRewriterResult rewriteCode( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { @@ -124,6 +125,7 @@ protected void rewriteCode( } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } enum RemoveCheckCastInstructionIfTrivialResult { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java index 4f8ee2e80f..a08d82a512 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java @@ -10,6 +10,7 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.Switch; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -33,7 +34,7 @@ protected String getTimingId() { } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { assert code.isConsistentGraph(appView); List blocksToRemove = new ArrayList<>(); // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove @@ -73,6 +74,7 @@ protected void rewriteCode(IRCode code) { } assert removedTrivialGotos(code); assert code.isConsistentGraph(appView); + return CodeRewriterResult.NONE; } @Override diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java new file mode 100644 index 0000000000..68d5c0d2ff --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java @@ -0,0 +1,40 @@ +// Copyright (c) 2023, 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.ir.conversion.passes.result; + +import com.android.tools.r8.errors.Unreachable; + +public interface CodeRewriterResult { + + CodeRewriterResult NO_CHANGE = new DefaultCodeRewriterResult(false); + CodeRewriterResult HAS_CHANGED = new DefaultCodeRewriterResult(true); + CodeRewriterResult NONE = + new CodeRewriterResult() { + @Override + public boolean hasChanged() { + throw new Unreachable(); + } + }; + + static CodeRewriterResult hasChanged(boolean hasChanged) { + return hasChanged ? HAS_CHANGED : NO_CHANGE; + } + + class DefaultCodeRewriterResult implements CodeRewriterResult { + + private final boolean hasChanged; + + public DefaultCodeRewriterResult(boolean hasChanged) { + this.hasChanged = hasChanged; + } + + @Override + public boolean hasChanged() { + return hasChanged; + } + } + + boolean hasChanged(); +} 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 4984711307..5b78fcfea6 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 @@ -5,13 +5,11 @@ package com.android.tools.r8.ir.optimize; import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; -import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; @@ -35,14 +33,12 @@ import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InvokeInterface; -import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.SyntheticPosition; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.LazyBox; @@ -59,7 +55,6 @@ import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.Set; @@ -68,11 +63,9 @@ public class CodeRewriter { private final AppView appView; private final DexItemFactory dexItemFactory; - private final InternalOptions options; public CodeRewriter(AppView appView) { this.appView = appView; - this.options = appView.options(); this.dexItemFactory = appView.dexItemFactory(); } @@ -134,112 +127,6 @@ public static void removeAssumeInstructions(AppView appView, IRCode code) { assert Streams.stream(code.instructions()).noneMatch(Instruction::isAssume); } - private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) { - // TODO(sgjesse): Insert cast if required. - TypeElement returnType = - TypeElement.fromDexType(invoke.getInvokedMethod().proto.returnType, maybeNull(), appView); - TypeElement argumentType = - TypeElement.fromDexType(getArgumentType(invoke, argumentIndex), maybeNull(), appView); - return appView.enableWholeProgramOptimizations() - ? argumentType.lessThanOrEqual(returnType, appView) - : argumentType.equals(returnType); - } - - private DexType getArgumentType(InvokeMethod invoke, int argumentIndex) { - if (invoke.isInvokeStatic()) { - return invoke.getInvokedMethod().proto.parameters.values[argumentIndex]; - } - if (argumentIndex == 0) { - return invoke.getInvokedMethod().holder; - } - return invoke.getInvokedMethod().proto.parameters.values[argumentIndex - 1]; - } - - // Replace result uses for methods where something is known about what is returned. - public boolean rewriteMoveResult(IRCode code) { - if (options.isGeneratingClassFiles() || !code.metadata().mayHaveInvokeMethod()) { - return false; - } - - AssumeRemover assumeRemover = new AssumeRemover(appView, code); - boolean changed = false; - boolean mayHaveRemovedTrivialPhi = false; - Set blocksToBeRemoved = Sets.newIdentityHashSet(); - ListIterator blockIterator = code.listIterator(); - while (blockIterator.hasNext()) { - BasicBlock block = blockIterator.next(); - if (blocksToBeRemoved.contains(block)) { - continue; - } - - InstructionListIterator iterator = block.listIterator(code); - while (iterator.hasNext()) { - InvokeMethod invoke = iterator.next().asInvokeMethod(); - if (invoke == null || !invoke.hasOutValue() || invoke.outValue().hasLocalInfo()) { - continue; - } - - // Check if the invoked method is known to return one of its arguments. - DexClassAndMethod target = invoke.lookupSingleTarget(appView, code.context()); - if (target == null) { - continue; - } - - MethodOptimizationInfo optimizationInfo = target.getDefinition().getOptimizationInfo(); - if (!optimizationInfo.returnsArgument()) { - continue; - } - - int argumentIndex = optimizationInfo.getReturnedArgument(); - // Replace the out value of the invoke with the argument and ignore the out value. - if (argumentIndex < 0 || !checkArgumentType(invoke, argumentIndex)) { - continue; - } - - Value argument = invoke.arguments().get(argumentIndex); - Value outValue = invoke.outValue(); - assert outValue.verifyCompatible(argument.outType()); - - // Make sure that we are only narrowing information here. Note, in cases where we cannot - // find the definition of types, computing lessThanOrEqual will return false unless it is - // object. - if (!argument.getType().lessThanOrEqual(outValue.getType(), appView)) { - continue; - } - - Set affectedValues = - argument.getType().equals(outValue.getType()) - ? Collections.emptySet() - : outValue.affectedValues(); - - assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue); - mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0; - outValue.replaceUsers(argument); - invoke.setOutValue(null); - changed = true; - - if (!affectedValues.isEmpty()) { - new TypeAnalysis(appView).narrowing(affectedValues); - } - } - } - assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); - Set affectedValues = Sets.newIdentityHashSet(); - if (!blocksToBeRemoved.isEmpty()) { - code.removeBlocks(blocksToBeRemoved); - code.removeAllDeadAndTrivialPhis(affectedValues); - assert code.getUnreachableBlocks().isEmpty(); - } else if (mayHaveRemovedTrivialPhi || assumeRemover.mayHaveIntroducedTrivialPhi()) { - code.removeAllDeadAndTrivialPhis(affectedValues); - } - if (!affectedValues.isEmpty()) { - new TypeAnalysis(appView).narrowing(affectedValues); - } - code.removeRedundantBlocks(); - assert code.isConsistentSSA(appView); - return changed; - } - public static void removeOrReplaceByDebugLocalWrite( Instruction currentInstruction, InstructionListIterator it, Value inValue, Value outValue) { if (outValue.hasLocalInfo() && outValue.getLocalInfo() != inValue.getLocalInfo()) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index a2074860e1..e9b180c578 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java @@ -21,6 +21,7 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueIsDeadAnalysis; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; +import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.IterableUtils; @@ -35,21 +36,15 @@ public class DeadCodeRemover { private final AppView appView; - private final CodeRewriter codeRewriter; - public DeadCodeRemover(AppView appView, CodeRewriter codeRewriter) { + public DeadCodeRemover(AppView appView) { this.appView = appView; - this.codeRewriter = codeRewriter; - } - - public CodeRewriter getCodeRewriter() { - return codeRewriter; } public void run(IRCode code, Timing timing) { timing.begin("Remove dead code"); - codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, timing); BranchSimplifier branchSimplifier = new BranchSimplifier(appView); @@ -75,7 +70,7 @@ public void run(IRCode code, Timing timing) { } public boolean verifyNoDeadCode(IRCode code) { - assert !codeRewriter.rewriteMoveResult(code); + assert !new MoveResultRewriter(appView).run(code, Timing.empty()).hasChanged(); assert !removeUnneededCatchHandlers(code); ValueIsDeadAnalysis valueIsDeadAnalysis = new ValueIsDeadAnalysis(appView, code); for (BasicBlock block : code.blocks) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java index c94b03ed22..46ed560696 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java @@ -34,6 +34,7 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.ArrayUtils; @@ -60,8 +61,9 @@ protected String getTimingId() { } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { rewriteConstantEnumMethodCalls(code); + return CodeRewriterResult.NONE; } @Override diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java index 9cd00c7b17..1cedcc33ca 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java @@ -62,6 +62,7 @@ import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer; import com.android.tools.r8.ir.conversion.SourceCode; +import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -1368,7 +1369,7 @@ public void performOutlining( applyOutliningCandidate(code); converter.printMethod(code, "IR after outlining (SSA)", null); converter.memberValuePropagation.run(code); - converter.codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, Timing.empty()); converter.removeDeadCodeAndFinalizeIR( code, OptimizationFeedbackIgnore.getInstance(), Timing.empty()); }, @@ -1399,7 +1400,7 @@ private void forEachSelectedOutliningMethod( // optimizations needed for outlining: rewriteMoveResult() to remove out-values on // StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove // unused out-values. - converter.codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, Timing.empty()); converter.deadCodeRemover.run(code, Timing.empty()); CodeRewriter.removeAssumeInstructions(appView, code); consumer.accept(code); diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java index 79165d119d..48312bdc07 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java @@ -47,7 +47,7 @@ public class EnqueuerDeferredTracingRewriter { EnqueuerDeferredTracingRewriter(AppView appView) { this.appView = appView; this.codeRewriter = new CodeRewriter(appView); - this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter); + this.deadCodeRemover = new DeadCodeRemover(appView); } public CodeRewriter getCodeRewriter() { From 3c1765130345b57eb63e695417df2bc4f121ac54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 15 Jun 2023 13:27:38 +0200 Subject: [PATCH 116/153] StringBuilderAppendOptimizer as CodeRewriterPass Bug: b/284304606 Change-Id: Idbea2ef38aa7609578d27a0d554b97734562df6e --- .../tools/r8/ir/conversion/IRConverter.java | 4 +- .../string/StringBuilderAppendOptimizer.java | 38 +++++++++++-------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 6007ea9f97..8522b133a4 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -753,9 +753,7 @@ Timing optimize( new ArrayConstructionSimplifier(appView).run(code, timing); new MoveResultRewriter(appView).run(code, timing); if (options.enableStringConcatenationOptimization && !isDebugMode) { - timing.begin("Rewrite string concat"); - StringBuilderAppendOptimizer.run(appView, code); - timing.end(); + new StringBuilderAppendOptimizer(appView).run(code, timing); } timing.begin("Propagate sparse conditionals"); new SparseConditionalConstantPropagation(appView, code).run(); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java index 2798fb0b8f..2ff8ec9db1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java @@ -16,6 +16,7 @@ import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createToStringNode; import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult; import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraProceduralDataflowAnalysisOptions; @@ -30,6 +31,8 @@ import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.AppendNode; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.ImplicitToStringNode; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitNode; @@ -75,29 +78,33 @@ * *

Finally, based on all optimizations, the IR is updated to reflect the optimizations. */ -public class StringBuilderAppendOptimizer { +public class StringBuilderAppendOptimizer extends CodeRewriterPass { - private final AppView appView; private final StringBuilderOracle oracle; - private final IRCode code; - private static final int NUMBER_OF_MUNCHING_PASSES = 3; - private StringBuilderAppendOptimizer(AppView appView, IRCode code) { - this.appView = appView; - this.code = code; + public StringBuilderAppendOptimizer(AppView appView) { + super(appView); oracle = new DefaultStringBuilderOracle(appView.dexItemFactory()); } - public static void run(AppView appView, IRCode code) { - new StringBuilderAppendOptimizer(appView, code).run(); + @Override + protected String getTimingId() { + return "StringBuilderAppendOptimizer"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return code.metadata().mayHaveNewInstance() + || code.metadata().mayHaveInvokeMethodWithReceiver(); } - private void run() { - Map stringBuilderGraphs = computeStringBuilderGraphs(); - Map actions = optimizeOnGraphs(stringBuilderGraphs); + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + Map stringBuilderGraphs = computeStringBuilderGraphs(code); + Map actions = optimizeOnGraphs(code, stringBuilderGraphs); if (actions.isEmpty()) { - return; + return CodeRewriterResult.NO_CHANGE; } InstructionListIterator it = code.instructionListIterator(); while (it.hasNext()) { @@ -108,6 +115,7 @@ private void run() { } } code.removeAllDeadAndTrivialPhis(); + return CodeRewriterResult.HAS_CHANGED; } private static class StringBuilderGraphState { @@ -137,7 +145,7 @@ private StringBuilderGraphState( * answer, for a given instruction, is a string builder value escaping and all escaped values * at the instruction. */ - private Map computeStringBuilderGraphs() { + private Map computeStringBuilderGraphs(IRCode code) { StringBuilderEscapeTransferFunction transferFunction = new StringBuilderEscapeTransferFunction(oracle); IntraproceduralDataflowAnalysis analysis = @@ -516,7 +524,7 @@ protected TraversalContinuation joiner( * the munching and care about performance. */ private Map optimizeOnGraphs( - Map stringBuilderGraphs) { + IRCode code, Map stringBuilderGraphs) { Map actions = new IdentityHashMap<>(); // Build state to allow munching over the string builder graphs. Map newInstances = new IdentityHashMap<>(); From 0c87082e8cca789818a2567f972b20086cfbc532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 15 Jun 2023 14:25:36 +0200 Subject: [PATCH 117/153] Fix unused import Change-Id: Ice124d29f4510881c5df53ebca9e671ecb1b5d11 --- src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java | 1 - 1 file changed, 1 deletion(-) 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 5b78fcfea6..e6968798e7 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 @@ -40,7 +40,6 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; -import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; From d5f5fd618312b73df1491388ec6bf06d3fe5d87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 15 Jun 2023 14:50:35 +0200 Subject: [PATCH 118/153] Fix redundant blocks Change-Id: I309771d42590ccd52861ebb8d5a2c81399450efa --- .../r8/horizontalclassmerging/code/ClassInitializerMerger.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java index 09e9911ae4..b4d26cdf75 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java @@ -280,6 +280,9 @@ public IRCode buildIR( // Cleanup. code.removeBlocks(blocksToRemove); code.removeAllDeadAndTrivialPhis(); + code.removeRedundantBlocks(); + + assert code.isConsistentSSA(appView); return code; } From 89d99b4baa58d1e4f01bdb4f94cb2ed8d96abe13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Thu, 15 Jun 2023 14:54:50 +0200 Subject: [PATCH 119/153] Make SparseConditionalConstantPropagation a CodeRewriterPass Bug: b/284304606 Change-Id: I2a7948133b04613d148dcc88fd6758ef0c91e189 --- .../SparseConditionalConstantPropagation.java | 31 +++++++++++++------ .../tools/r8/ir/conversion/IRConverter.java | 4 +-- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java index e6a6d31b56..ec226d9055 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.analysis.constant; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.code.BasicBlock; @@ -16,6 +17,8 @@ import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.StringSwitch; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.BitSet; @@ -31,9 +34,8 @@ * "Constant Propagation with Conditional Branches". * https://www.cs.utexas.edu/users/lin/cs380c/wegman.pdf */ -public class SparseConditionalConstantPropagation { +public class SparseConditionalConstantPropagation extends CodeRewriterPass { - private final AppView appView; private final IRCode code; private final Map mapping = new HashMap<>(); // TODO(b/270398965): Replace LinkedList. @@ -43,19 +45,29 @@ public class SparseConditionalConstantPropagation { @SuppressWarnings("JdkObsolete") private final Deque flowEdges = new LinkedList<>(); - private final int maxBlockNumber; private final BitSet[] executableFlowEdges; private final BitSet visitedBlocks; public SparseConditionalConstantPropagation(AppView appView, IRCode code) { - this.appView = appView; + super(appView); this.code = code; - maxBlockNumber = code.getCurrentBlockNumber() + 1; + int maxBlockNumber = code.getCurrentBlockNumber() + 1; executableFlowEdges = new BitSet[maxBlockNumber]; visitedBlocks = new BitSet(maxBlockNumber); } - public void run() { + @Override + protected String getTimingId() { + return "SparseConditionalConstantPropagation"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return true; + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { BasicBlock firstBlock = code.entryBlock(); visitInstructions(firstBlock); @@ -82,11 +94,12 @@ public void run() { } } } - rewriteCode(); + rewriteConstants(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } - private void rewriteCode() { + private void rewriteConstants() { Set affectedValues = Sets.newIdentityHashSet(); List blockToAnalyze = new ArrayList<>(); mapping.entrySet().stream() @@ -258,7 +271,7 @@ private void addFlowEdgesForJumpInstruction(JumpInstruction jumpInstruction) { private void setExecutableEdge(int from, int to) { BitSet previousExecutable = executableFlowEdges[to]; if (previousExecutable == null) { - previousExecutable = new BitSet(maxBlockNumber); + previousExecutable = new BitSet(executableFlowEdges.length); executableFlowEdges[to] = previousExecutable; } previousExecutable.set(from); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 8522b133a4..a7d54cabdc 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -755,9 +755,7 @@ Timing optimize( if (options.enableStringConcatenationOptimization && !isDebugMode) { new StringBuilderAppendOptimizer(appView).run(code, timing); } - timing.begin("Propagate sparse conditionals"); - new SparseConditionalConstantPropagation(appView, code).run(); - timing.end(); + new SparseConditionalConstantPropagation(appView, code).run(code, timing); timing.begin("Rewrite always throwing instructions"); new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(code); timing.end(); From b7b2064c97b1e6b9484d026fb65fb4b5b8a4a618 Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Thu, 15 Jun 2023 14:57:51 +0200 Subject: [PATCH 120/153] Only cache succeeding runs If art flakes, or one stops an art execution, we might get results into the cache that are incorrect (and will keep reporting failure) Bug: 286019067 Change-Id: I447069a9ff16ba9d2fbf8f16fa7c4649f2ac1f85 --- src/test/java/com/android/tools/r8/ToolHelper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java index 2d41bdba13..55994a21df 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java @@ -593,7 +593,9 @@ private boolean useCache() { } public void cacheResult(ProcessResult result) { - if (useCache()) { + // Only cache succeding runs, otherwise a flaky or killed art run can + // put invalid entries into the cache. + if (useCache() && result.exitCode == 0) { assert artResultCacheLookupKey != null; CommandResultCache.getInstance().putResult(result, artResultCacheLookupKey); } From 02f224dccb8a103de3ce79172a76be534281a9da Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 15 Jun 2023 14:12:58 +0200 Subject: [PATCH 121/153] Add a test for propagating definitely set/unset bits Bug: b/196017578 Change-Id: I8180be7e9027d9f133cc184941bfb26f14320ca1 --- .../KotlinDefaultArgumentsTest.java | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/ir/optimize/constantpropagation/KotlinDefaultArgumentsTest.java diff --git a/src/test/java/com/android/tools/r8/ir/optimize/constantpropagation/KotlinDefaultArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/constantpropagation/KotlinDefaultArgumentsTest.java new file mode 100644 index 0000000000..9c951c1aa7 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/constantpropagation/KotlinDefaultArgumentsTest.java @@ -0,0 +1,177 @@ +// Copyright (c) 2023, 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.ir.optimize.constantpropagation; + +import static com.android.tools.r8.ir.optimize.constantpropagation.KotlinDefaultArgumentsTest.Greeter.getHello; +import static com.android.tools.r8.ir.optimize.constantpropagation.KotlinDefaultArgumentsTest.Greeter.getWorld; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class KotlinDefaultArgumentsTest extends TestBase { + + enum Effect { + MATERIALIZED, + NONE, + REMOVED + } + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testFirstBranchRemoved() throws Exception { + test(MainRemovedNone.class, Effect.REMOVED, Effect.NONE); + } + + @Test + public void testBothBranchesRemoved() throws Exception { + test(MainRemovedRemoved.class, Effect.REMOVED, Effect.REMOVED); + } + + @Test + public void testFirstBranchRemovedAndSecondBranchMaterialized() throws Exception { + test(MainRemovedMaterialized.class, Effect.REMOVED, Effect.MATERIALIZED); + } + + @Test + public void testFirstBranchMaterialized() throws Exception { + test(MainMaterializedNone.class, Effect.MATERIALIZED, Effect.NONE); + } + + @Test + public void testFirstBranchMaterializedAndSecondBranchRemoved() throws Exception { + test(MainMaterializedRemoved.class, Effect.MATERIALIZED, Effect.REMOVED); + } + + @Test + public void testBothBranchesMaterialized() throws Exception { + test(MainMaterializedMaterialized.class, Effect.MATERIALIZED, Effect.MATERIALIZED); + } + + private void test(Class mainClass, Effect firstBranchEffect, Effect secondBranchEffect) + throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(mainClass, Greeter.class) + .addKeepMainRule(mainClass) + .enableInliningAnnotations() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + // TODO(b/196017578): If the branch effect is NONE, then check that the IF instruction + // AND its body are retained. + // TODO(b/196017578): If the branch effect is REMOVED, then check that the IF + // instruction AND its body is removed. + // TODO(b/196017578): If the branch effect is MATERIALIZED, then check that the IF + // instruction is removed but NOT its body. + // TODO(b/196017578): If the branch effect is MATERIALIZED, then check that the unused + // parameter is removed. + }) + .run(parameters.getRuntime(), mainClass) + .assertSuccessWithOutputLines("Hello, world!"); + } + + // Test where first parameter X is always given at the call site. + // The first branch in greet() should be removed. + static class MainRemovedNone { + + public static void main(String[] args) { + Greeter.greet(getHello(), null, 2); + if (System.currentTimeMillis() < 0) { + Greeter.greet(getHello(), getWorld(), 0); + } + } + } + + // Test where both parameters X and Y are always given at the call site. + // Both branches in greet() should be removed. + static class MainRemovedRemoved { + + public static void main(String[] args) { + Greeter.greet(getHello(), getWorld(), 0); + } + } + + // Test where the first parameter X is always given and the second parameter Y is never given at + // the call site. + // The first branch in greet() should be removed and the second branch in greet() should be + // "materialized". + static class MainRemovedMaterialized { + + public static void main(String[] args) { + Greeter.greet(getHello(), null, 2); + } + } + + // Test where the first parameter X is never given at the call site. + // The first branch in greet() should be materialized. + static class MainMaterializedNone { + + public static void main(String[] args) { + Greeter.greet(null, null, 3); + if (System.currentTimeMillis() < 0) { + Greeter.greet(null, getWorld(), 1); + } + } + } + + // Test where the first parameter X is never given and the second parameter Y is always given at + // the call site. + // The first branch in greet() should be materialized and the second branch should be removed. + static class MainMaterializedRemoved { + + public static void main(String[] args) { + Greeter.greet(null, getWorld(), 1); + } + } + + // Test where none of the parameters X and Y are given at the call site. + // Both branches in greet() should be materialized. + static class MainMaterializedMaterialized { + + public static void main(String[] args) { + Greeter.greet(null, null, 3); + } + } + + static class Greeter { + + @NeverInline + static void greet(String x, String y, int defaults) { + if ((defaults & 1) != 0) { + x = "Hello"; + } + if ((defaults & 2) != 0) { + y = ", world!"; + } + System.out.print(x); + System.out.println(y); + } + + @NeverInline + static String getHello() { + return System.currentTimeMillis() > 0 ? "Hello" : ""; + } + + @NeverInline + static String getWorld() { + return System.currentTimeMillis() > 0 ? ", world!" : ""; + } + } +} From 9cde2a1daa4942fffa17c92994260c4159c3fbb2 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 15 Jun 2023 19:44:09 +0200 Subject: [PATCH 122/153] Track int parameters with bitwise AND operations Bug: b/196017578 Change-Id: Ib6c820cce0c3069c7d3a49113182caf1535c9b95 --- ...escriptionMethodOptimizationInfoFixer.java | 8 ++--- .../MethodOptimizationFeedback.java | 3 ++ .../info/DefaultMethodOptimizationInfo.java | 10 ++++++ .../optimize/info/MethodOptimizationInfo.java | 4 +++ .../info/MethodOptimizationInfoCollector.java | 36 ++++++++++++++++++- .../info/MethodOptimizationInfoFixer.java | 2 +- .../info/MutableMethodOptimizationInfo.java | 34 ++++++++++++++++-- .../info/OptimizationFeedbackDelayed.java | 7 ++++ .../info/OptimizationFeedbackIgnore.java | 4 +++ .../info/OptimizationFeedbackSimple.java | 9 +++++ 10 files changed, 109 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java index 6a72244bd7..a897bd00ed 100644 --- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java +++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java @@ -149,12 +149,12 @@ public SimpleInliningConstraint fixupSimpleInliningConstraint( } /** - * Function for rewriting the unused arguments on a piece of method optimization info after - * prototype changes were made. + * Function for rewriting a BitSet that stores a bit per argument on a piece of method + * optimization info after prototype changes were made. */ @Override - public BitSet fixupUnusedArguments(BitSet unusedArguments) { - return fixupArgumentInfo(unusedArguments); + public BitSet fixupArguments(BitSet arguments) { + return fixupArgumentInfo(arguments); } private BitSet fixupArgumentInfo(BitSet bitSet) { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java index c1a7b55a6b..7327760feb 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java @@ -75,6 +75,9 @@ void setInstanceInitializerInfoCollection( void classInitializerMayBePostponed(DexEncodedMethod method); + void setParametersWithBitwiseOperations( + ProgramMethod method, BitSet parametersWithBitwiseOperations); + void setUnusedArguments(ProgramMethod method, BitSet unusedArguments); // Unset methods. diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java index b1e1a69044..f596d2ae07 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java @@ -147,6 +147,16 @@ public SimpleInliningConstraint getSimpleInliningConstraint() { return NeverSimpleInliningConstraint.getInstance(); } + @Override + public boolean hasParametersWithBitwiseOperations() { + return false; + } + + @Override + public BitSet getParametersWithBitwiseOperations() { + return null; + } + @Override public BitSet getUnusedArguments() { return null; diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java index 0d42b62c3e..31775ae893 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java @@ -80,6 +80,10 @@ public final boolean hasNonNullParamOnNormalExits() { public abstract SimpleInliningConstraint getSimpleInliningConstraint(); + public abstract boolean hasParametersWithBitwiseOperations(); + + public abstract BitSet getParametersWithBitwiseOperations(); + public final boolean hasUnusedArguments() { assert getUnusedArguments() == null || !getUnusedArguments().isEmpty(); return getUnusedArguments() != null; diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java index ba4ba646ab..892f856017 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java @@ -101,6 +101,7 @@ import com.android.tools.r8.kotlin.Kotlin; import com.android.tools.r8.kotlin.Kotlin.Intrinsics; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Timing; import com.google.common.collect.Sets; @@ -156,6 +157,7 @@ public void collectMethodOptimizationInfo( computeReturnValueOnlyDependsOnArguments(feedback, definition, code, timing); BitSet nonNullParamOrThrow = computeNonNullParamOrThrow(feedback, definition, code, timing); computeNonNullParamOnNormalExits(feedback, code, nonNullParamOrThrow, timing); + computeParametersWithBitwiseOperations(method, code, feedback, timing); computeUnusedArguments(method, code, feedback, timing); } @@ -1173,6 +1175,36 @@ private boolean isNormalExitDominated( return true; } + private void computeParametersWithBitwiseOperations( + ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) { + timing.begin("Compute parameters with bitwise operations"); + computeParametersWithBitwiseOperations(method, code, feedback); + timing.end(); + } + + private void computeParametersWithBitwiseOperations( + ProgramMethod method, IRCode code, OptimizationFeedback feedback) { + BitSet parametersWithBitwiseOperations = new BitSet(method.getParameters().size()); + InstructionIterator instructionIterator = code.entryBlock().iterator(); + Argument argument = instructionIterator.next().asArgument(); + while (argument != null) { + if (hasBitwiseOperation(argument)) { + int parameterIndex = + argument.getIndex() - BooleanUtils.intValue(method.getDefinition().isInstance()); + parametersWithBitwiseOperations.set(parameterIndex); + } + argument = instructionIterator.next().asArgument(); + } + if (!parametersWithBitwiseOperations.isEmpty()) { + feedback.setParametersWithBitwiseOperations(method, parametersWithBitwiseOperations); + } + } + + private boolean hasBitwiseOperation(Argument argument) { + return argument.getOutType().isInt() + && argument.outValue().hasUserThatMatches(Instruction::isAnd); + } + private void computeUnusedArguments( ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) { timing.begin("Compute unused arguments"); @@ -1191,6 +1223,8 @@ private void computeUnusedArguments( } argument = instructionIterator.next().asArgument(); } - feedback.setUnusedArguments(method, unusedArguments); + if (!unusedArguments.isEmpty()) { + feedback.setUnusedArguments(method, unusedArguments); + } } } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java index 0044499213..4279b3caa2 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java @@ -43,5 +43,5 @@ public abstract SimpleInliningConstraint fixupSimpleInliningConstraint( SimpleInliningConstraint constraint, SimpleInliningConstraintFactory factory); - public abstract BitSet fixupUnusedArguments(BitSet unusedArguments); + public abstract BitSet fixupArguments(BitSet arguments); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java index 7dcb035223..555bd6f39e 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java @@ -79,6 +79,7 @@ public class MutableMethodOptimizationInfo extends MethodOptimizationInfo NeverSimpleInliningConstraint.getInstance(); private int maxRemovedAndroidLogLevel = MaximumRemovedAndroidLogLevelRule.NOT_SET; + private BitSet parametersWithBitwiseOperations = null; private BitSet unusedArguments = null; // To reduce the memory footprint of UpdatableMethodOptimizationInfo, all the boolean fields are @@ -159,6 +160,7 @@ public MutableMethodOptimizationInfo fixup( .fixupNonNullParamOnNormalExits(fixer) .fixupNonNullParamOrThrow(fixer) .fixupReturnedArgumentIndex(fixer) + .fixupParametersWithBitwiseOperations(fixer) .fixupSimpleInliningConstraint(appView, fixer) .fixupUnusedArguments(fixer); } @@ -434,14 +436,42 @@ public SimpleInliningConstraint getSimpleInliningConstraint() { return simpleInliningConstraint; } + @Override + public boolean hasParametersWithBitwiseOperations() { + return parametersWithBitwiseOperations != null; + } + + @Override + public BitSet getParametersWithBitwiseOperations() { + return parametersWithBitwiseOperations; + } + + public void setParametersWithBitwiseOperations(BitSet parametersWithBitwiseOperations) { + if (parametersWithBitwiseOperations != null && !parametersWithBitwiseOperations.isEmpty()) { + this.parametersWithBitwiseOperations = parametersWithBitwiseOperations; + } else { + this.parametersWithBitwiseOperations = null; + } + } + + public MutableMethodOptimizationInfo fixupParametersWithBitwiseOperations( + MethodOptimizationInfoFixer fixer) { + return fixupParametersWithBitwiseOperations(fixer.fixupArguments(unusedArguments)); + } + + public MutableMethodOptimizationInfo fixupParametersWithBitwiseOperations( + BitSet parametersWithBitwiseOperations) { + setParametersWithBitwiseOperations(parametersWithBitwiseOperations); + return this; + } + @Override public BitSet getUnusedArguments() { return unusedArguments; } public MutableMethodOptimizationInfo fixupUnusedArguments(MethodOptimizationInfoFixer fixer) { - fixupUnusedArguments(fixer.fixupUnusedArguments(unusedArguments)); - return this; + return fixupUnusedArguments(fixer.fixupArguments(unusedArguments)); } public MutableMethodOptimizationInfo fixupUnusedArguments(BitSet unusedArguments) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java index 2148519f55..bbb1ceca45 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java @@ -276,6 +276,13 @@ public synchronized void classInitializerMayBePostponed(DexEncodedMethod method) getMethodOptimizationInfoForUpdating(method).markClassInitializerMayBePostponed(); } + @Override + public void setParametersWithBitwiseOperations( + ProgramMethod method, BitSet parametersWithBitwiseOperations) { + getMethodOptimizationInfoForUpdating(method) + .setParametersWithBitwiseOperations(parametersWithBitwiseOperations); + } + @Override public synchronized void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) { getMethodOptimizationInfoForUpdating(method).setUnusedArguments(unusedArguments); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java index 3f46ce1414..c3ff56af5a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java @@ -125,6 +125,10 @@ public void setSimpleInliningConstraint( @Override public void classInitializerMayBePostponed(DexEncodedMethod method) {} + @Override + public void setParametersWithBitwiseOperations( + ProgramMethod method, BitSet parametersWithBitwiseOperations) {} + @Override public void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {} diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java index a0c9863253..51c5bb6c1c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java @@ -212,6 +212,15 @@ public void classInitializerMayBePostponed(DexEncodedMethod method) { method.getMutableOptimizationInfo().markClassInitializerMayBePostponed(); } + @Override + public void setParametersWithBitwiseOperations( + ProgramMethod method, BitSet parametersWithBitwiseOperations) { + method + .getDefinition() + .getMutableOptimizationInfo() + .setParametersWithBitwiseOperations(parametersWithBitwiseOperations); + } + @Override public void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) { method.getDefinition().getMutableOptimizationInfo().setUnusedArguments(unusedArguments); From 7fd5031466267c2dcd5022890440720959937333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Fri, 16 Jun 2023 08:21:51 +0200 Subject: [PATCH 123/153] Add removeRedundant blocks Change-Id: Ib18a977755bd388cf6505f055dc3763391036efc --- .../tools/r8/ir/conversion/IRToDexFinalizer.java | 11 ++++++++--- .../passes/ArrayConstructionSimplifier.java | 2 ++ .../r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java index dbd35f7911..8c161b84c1 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java @@ -38,6 +38,13 @@ public DexCode finalizeCode( } DexEncodedMethod method = code.method(); code.traceBlocks(); + workaroundBugs(code); + // Perform register allocation. + RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing); + return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build(); + } + + private void workaroundBugs(IRCode code) { RuntimeWorkaroundCodeRewriter.workaroundNumberConversionRegisterAllocationBug(code, options); // Workaround massive dex2oat memory use for self-recursive methods. RuntimeWorkaroundCodeRewriter.workaroundDex2OatInliningIssue(appView, code); @@ -46,9 +53,7 @@ public DexCode finalizeCode( RuntimeWorkaroundCodeRewriter.workaroundDex2OatLinkedListBug(code, options); RuntimeWorkaroundCodeRewriter.workaroundForwardingInitializerBug(code, options); RuntimeWorkaroundCodeRewriter.workaroundExceptionTargetingLoopHeaderBug(code, options); - // Perform register allocation. - RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing); - return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build(); + assert code.isConsistentSSA(appView); } private RegisterAllocator performRegisterAllocation( diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java index 61cc2c1737..d6c709b9fd 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java @@ -99,6 +99,8 @@ protected CodeRewriterResult rewriteCode(IRCode code) { BasicBlock block = worklist.next(); simplifyArrayConstructionBlock(block, worklist, code, appView.options()); } + // Do only when the rewriter pass has changed something. + code.removeRedundantBlocks(); return CodeRewriterResult.NONE; } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java index 370a5d74b8..855f98550d 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java @@ -66,6 +66,7 @@ public static void workaroundDex2OatInliningIssue(AppView appView, IRCode cod code.blocks.add(rethrowBlock); // Add catch handler to the block containing the last recursive call. newBlock.appendCatchHandler(rethrowBlock, guard); + code.removeRedundantBlocks(); } } From 18fbe5bdb98d76675ae550f6009b6729260e488b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Fri, 16 Jun 2023 10:20:18 +0200 Subject: [PATCH 124/153] Add redundant block removal Change-Id: I38450952a807d421ce2b635311989fd495d709f8 --- .../tools/r8/ir/conversion/passes/MoveResultRewriter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java index 4481aa0d7f..18c3757485 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java @@ -118,6 +118,9 @@ protected CodeRewriterResult rewriteCode(IRCode code) { if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } + if (changed) { + code.removeRedundantBlocks(); + } assert code.isConsistentSSA(appView); return CodeRewriterResult.hasChanged(changed); } From dedf8734dd09b3d95ea9452e5c91059251b60a93 Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Fri, 16 Jun 2023 11:32:22 +0200 Subject: [PATCH 125/153] [LIR] Use LIR in R8 conversion and inlining Bug: b/225838009 Change-Id: I02f5df60c983fed94122939721d59d90917eace6 --- .../java/com/android/tools/r8/graph/Code.java | 9 + .../fieldaccess/FieldAccessAnalysis.java | 38 +- .../FieldReadForInvokeReceiverAnalysis.java | 2 +- .../FieldReadForWriteAnalysis.java | 2 +- .../tools/r8/ir/conversion/IRConverter.java | 61 +++- .../ir/conversion/PrimaryR8IRConverter.java | 22 ++ .../records/RecordFieldValuesRewriter.java | 1 + .../ClassInlinerCostAnalysis.java | 2 +- .../tools/r8/lightir/IR2LirConverter.java | 12 +- .../tools/r8/lightir/Lir2IRConverter.java | 152 +++++++- .../android/tools/r8/lightir/LirBuilder.java | 15 +- .../com/android/tools/r8/lightir/LirCode.java | 234 +++++++++++- .../tools/r8/lightir/LirDecodingStrategy.java | 11 + .../android/tools/r8/lightir/LirOpcodes.java | 5 +- .../lightir/LirParsedInstructionCallback.java | 28 +- .../android/tools/r8/lightir/LirPrinter.java | 19 +- .../tools/r8/lightir/LirSizeEstimation.java | 339 ++++++++++++++++++ .../android/tools/r8/lightir/LirStrategy.java | 24 +- .../r8/lightir/LirUseRegistryCallback.java | 153 ++++++++ .../r8/lightir/PhiInInstructionsStrategy.java | 16 +- .../tools/r8/utils/InternalOptions.java | 16 +- .../tools/r8/R8RunExamplesAndroidOTest.java | 6 +- ...DaggerBasicNotSingletonUsingBindsTest.java | 3 +- ...ssInlinerPhiDirectUserAfterInlineTest.java | 6 +- .../classinliner/ClassInlinerTest.java | 12 +- .../FieldReadForWriteTest.java | 2 +- .../r8/lightir/LirBasicCallbackTest.java | 11 +- .../r8/regress/Regress160394262Test.java | 11 +- 28 files changed, 1112 insertions(+), 100 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java create mode 100644 src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java index a76bdf5a89..3332852d35 100644 --- a/src/main/java/com/android/tools/r8/graph/Code.java +++ b/src/main/java/com/android/tools/r8/graph/Code.java @@ -15,6 +15,7 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.PositionBuilder; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; +import com.android.tools.r8.lightir.LirCode; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.RetracerForCodePrinting; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -148,6 +149,14 @@ public boolean estimatedSizeForInliningAtMost(int threshold) { public abstract int estimatedDexCodeSizeUpperBoundInBytes(); + public final boolean isLirCode() { + return asLirCode() != null; + } + + public LirCode asLirCode() { + return null; + } + public CfCode asCfCode() { throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()"); } diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java index d871616373..ca741c9f7b 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java @@ -36,8 +36,14 @@ public FieldAccessAnalysis(AppView appView) { this.fieldBitAccessAnalysis = options.enableFieldBitAccessAnalysis ? new FieldBitAccessAnalysis() : null; this.fieldAssignmentTracker = new FieldAssignmentTracker(appView); - this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView); - this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); + if (options.testing.canUseLir(appView)) { + // When using LIR the bytecode metadata is computed later during finalization via IR. + this.fieldReadForInvokeReceiverAnalysis = null; + this.fieldReadForWriteAnalysis = null; + } else { + this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView); + this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); + } } @VisibleForTesting @@ -111,4 +117,32 @@ public void recordFieldAccesses( } } } + + public static BytecodeMetadataProvider computeBytecodeMetadata( + IRCode irCode, AppView appView) { + // This rebuilding of metadata should only be used in the LIR pipeline where the info is + // discarded when translating from IR to LIR. + assert appView.options().testing.canUseLir(appView); + BytecodeMetadataProvider bytecodeMetadataProvider = BytecodeMetadataProvider.empty(); + if (irCode.metadata().mayHaveFieldInstruction()) { + BytecodeMetadataProvider.Builder builder = BytecodeMetadataProvider.builder(); + FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis = + new FieldReadForInvokeReceiverAnalysis(appView); + FieldReadForWriteAnalysis fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); + for (Instruction instruction : irCode.instructions()) { + if (instruction.isFieldInstruction()) { + FieldInstruction fieldInstruction = instruction.asFieldInstruction(); + ProgramField field = + appView.appInfo().resolveField(fieldInstruction.getField()).getProgramField(); + if (field != null) { + fieldReadForInvokeReceiverAnalysis.recordFieldAccess( + fieldInstruction, field, builder, irCode.context()); + fieldReadForWriteAnalysis.recordFieldAccess(fieldInstruction, field, builder); + } + } + } + bytecodeMetadataProvider = builder.build(); + } + return bytecodeMetadataProvider; + } } diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java index 976020c60c..6289b5de6b 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java @@ -23,7 +23,7 @@ public class FieldReadForInvokeReceiverAnalysis { private final AppView appView; - FieldReadForInvokeReceiverAnalysis(AppView appView) { + public FieldReadForInvokeReceiverAnalysis(AppView appView) { this.appView = appView; } diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java index 6a5c4201b6..2155676af9 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java @@ -21,7 +21,7 @@ public class FieldReadForWriteAnalysis { private final AppView appView; - FieldReadForWriteAnalysis(AppView appView) { + public FieldReadForWriteAnalysis(AppView appView) { this.appView = appView; } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index a7d54cabdc..e9431230fa 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -616,12 +616,14 @@ Timing optimize( assertionsRewriter.run(method, code, deadCodeRemover, timing); CheckNotNullConverter.runIfNecessary(appView, code); + previous = printMethod(code, "IR after disable assertions (SSA)", previous); if (serviceLoaderRewriter != null) { assert appView.appInfo().hasLiveness(); timing.begin("Rewrite service loaders"); serviceLoaderRewriter.rewrite(code, methodProcessor, methodProcessingContext); timing.end(); + previous = printMethod(code, "IR after service rewriting (SSA)", previous); } if (identifierNameStringMarker != null) { @@ -629,12 +631,14 @@ Timing optimize( identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(code); timing.end(); assert code.isConsistentSSA(appView); + previous = printMethod(code, "IR after identifier-name strings (SSA)", previous); } if (memberValuePropagation != null) { timing.begin("Propagate member values"); memberValuePropagation.run(code); timing.end(); + previous = printMethod(code, "IR after member-value propagation (SSA)", previous); } if (enumValueOptimizer != null) { @@ -642,16 +646,16 @@ Timing optimize( timing.begin("Remove switch maps"); enumValueOptimizer.removeSwitchMaps(code); timing.end(); + previous = printMethod(code, "IR after enum-value optimization (SSA)", previous); } if (instanceInitializerOutliner != null) { instanceInitializerOutliner.rewriteInstanceInitializers( code, context, methodProcessor, methodProcessingContext); assert code.verifyTypes(appView); + previous = printMethod(code, "IR after instance initializer outlining (SSA)", previous); } - previous = printMethod(code, "IR after disable assertions (SSA)", previous); - // Update the IR code if collected call site optimization info has something useful. // While aggregation of parameter information at call sites would be more precise than static // types, those could be still less precise at one single call site, where specific arguments @@ -1061,7 +1065,11 @@ public void finalizeIR( if (options.testing.roundtripThroughLir) { code = roundtripThroughLir(code, feedback, bytecodeMetadataProvider, timing); } - if (options.isGeneratingClassFiles()) { + if (options.testing.canUseLir(appView)) { + timing.begin("IR->LIR"); + finalizeToLir(code, feedback, bytecodeMetadataProvider, timing); + timing.end(); + } else if (options.isGeneratingClassFiles()) { timing.begin("IR->CF"); finalizeToCf(code, feedback, bytecodeMetadataProvider, timing); timing.end(); @@ -1101,7 +1109,7 @@ private > IRCode doRoundtripWithStrategy( IRCode code, S strategy, String name, Timing timing) { timing.begin("IR->LIR (" + name + ")"); LirCode lirCode = - IR2LirConverter.translate(code, strategy.getEncodingStrategy(), appView.dexItemFactory()); + IR2LirConverter.translate(code, strategy.getEncodingStrategy(), appView.options()); timing.end(); // Check that printing does not fail. String lirString = lirCode.toString(); @@ -1109,11 +1117,26 @@ private > IRCode doRoundtripWithStrategy( timing.begin("LIR->IR (" + name + ")"); IRCode irCode = Lir2IRConverter.translate( - code.context(), lirCode, strategy.getDecodingStrategy(lirCode), appView); + code.context(), lirCode, strategy.getDecodingStrategy(lirCode, null), appView); timing.end(); return irCode; } + private void finalizeToLir( + IRCode code, + OptimizationFeedback feedback, + BytecodeMetadataProvider bytecodeMetadataProvider, + Timing timing) { + assert deadCodeRemover.verifyNoDeadCode(code); + assert BytecodeMetadataProvider.empty() == bytecodeMetadataProvider; + LirCode lirCode = + IR2LirConverter.translate( + code, LirStrategy.getDefaultStrategy().getEncodingStrategy(), appView.options()); + ProgramMethod method = code.context(); + method.setCode(lirCode, appView); + markProcessed(code, feedback); + } + private void finalizeToCf( IRCode code, OptimizationFeedback feedback, @@ -1251,4 +1274,32 @@ public void onMethodCodePruned(ProgramMethod method) { inliner.onMethodCodePruned(method); } } + + public void finalizeLirMethodToOutputFormat(ProgramMethod method) { + Code code = method.getDefinition().getCode(); + if (!(code instanceof LirCode)) { + return; + } + Timing onThreadTiming = Timing.empty(); + IRCode irCode = method.buildIR(appView); + BytecodeMetadataProvider bytecodeMetadataProvider = + FieldAccessAnalysis.computeBytecodeMetadata(irCode, appView.withLiveness()); + // During processing optimization info may cause previously live code to become dead. + // E.g., we may now have knowledge that an invoke does not have side effects. + // Thus, we re-run the dead-code remover now as it is assumed complete by CF/DEX finalization. + deadCodeRemover.run(irCode, onThreadTiming); + if (options.isGeneratingClassFiles()) { + method.setCode( + new IRToCfFinalizer(appView, deadCodeRemover) + .finalizeCode(irCode, bytecodeMetadataProvider, onThreadTiming), + appView); + } else { + assert options.isGeneratingDex(); + method.setCode( + new IRToDexFinalizer(appView, deadCodeRemover) + .finalizeCode(irCode, bytecodeMetadataProvider, onThreadTiming), + appView); + updateHighestSortingStrings(method.getDefinition()); + } + } } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java index f8946d4f76..1187f29fa9 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java @@ -16,6 +16,7 @@ import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.io.IOException; @@ -100,6 +101,7 @@ private DexApplication internalOptimize( lastWaveDone(postMethodProcessorBuilder, executorService); eventConsumer.finished(appView); assert appView.graphLens() == graphLensForPrimaryOptimizationPass; + finalizeLirToOutputFormat(timing, executorService); timing.end(); } @@ -176,6 +178,7 @@ private DexApplication internalOptimize( eventConsumer.finished(appView); assert appView.graphLens() == graphLensForSecondaryOptimizationPass; } + finalizeLirToOutputFormat(timing, executorService); timing.end(); } @@ -207,9 +210,28 @@ private DexApplication internalOptimize( // Assure that no more optimization feedback left after post processing. assert feedback.noUpdatesLeft(); + finalizeLirToOutputFormat(timing, executorService); return builder.build(); } + private void finalizeLirToOutputFormat(Timing timing, ExecutorService executorService) + throws ExecutionException { + if (!options.testing.canUseLir(appView)) { + return; + } + String output = options.isGeneratingClassFiles() ? "CF" : "DEX"; + timing.begin("LIR->IR->" + output); + ThreadUtils.processItems( + appView.appInfo().classes(), + clazz -> clazz.forEachProgramMethod(this::finalizeLirMethodToOutputFormat), + executorService); + appView + .getSyntheticItems() + .getPendingSyntheticClasses() + .forEach(clazz -> clazz.forEachProgramMethod(this::finalizeLirMethodToOutputFormat)); + timing.end(); + } + private void clearDexMethodCompilationState() { appView.appInfo().classes().forEach(this::clearDexMethodCompilationState); } diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java index 1653ae281f..6646ce092f 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java @@ -85,6 +85,7 @@ public void rewriteRecordFieldValues(ProgramMethod programMethod) { assert done; irConverter.removeDeadCodeAndFinalizeIR( irCode, OptimizationFeedbackIgnore.getInstance(), Timing.empty()); + irConverter.finalizeLirMethodToOutputFormat(programMethod); } public void rewriteRecordFieldArray( diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java index 12028fb665..cf4a6fefad 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java @@ -136,7 +136,7 @@ private int estimateSizeOfNonMaterializingInstructions(InvokeMethod invoke, IRCo break; case RETURN: - // Wil not materialize after class inlining. + // Will not materialize after class inlining. if (appView.options().isGeneratingClassFiles()) { result++; } else { diff --git a/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java index ee53ceb8f6..3bc2fe67a7 100644 --- a/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java +++ b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.lightir; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.CatchHandlers; @@ -12,6 +11,7 @@ import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; @@ -24,24 +24,22 @@ public class IR2LirConverter { - private final DexItemFactory factory; private final IRCode irCode; private final LirEncodingStrategy strategy; private final LirBuilder builder; private IR2LirConverter( - DexItemFactory factory, IRCode irCode, LirEncodingStrategy strategy) { - this.factory = factory; + InternalOptions options, IRCode irCode, LirEncodingStrategy strategy) { this.irCode = irCode; this.strategy = strategy; this.builder = - new LirBuilder<>(irCode.context().getReference(), strategy, factory) + new LirBuilder<>(irCode.context().getReference(), strategy, options) .setMetadata(irCode.metadata()); } public static LirCode translate( - IRCode irCode, LirEncodingStrategy strategy, DexItemFactory factory) { - return new IR2LirConverter<>(factory, irCode, strategy).internalTranslate(); + IRCode irCode, LirEncodingStrategy strategy, InternalOptions options) { + return new IR2LirConverter<>(options, irCode, strategy).internalTranslate(); } private void recordBlock(BasicBlock block, int blockIndex) { diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java index 8777089daf..4315a85582 100644 --- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java +++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java @@ -15,6 +15,11 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.proto.ArgumentInfo; +import com.android.tools.r8.graph.proto.ArgumentInfoCollection; +import com.android.tools.r8.graph.proto.RemovedArgumentInfo; +import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; +import com.android.tools.r8.graph.proto.RewrittenTypeInfo; import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement; import com.android.tools.r8.ir.analysis.type.TypeElement; @@ -25,6 +30,7 @@ import com.android.tools.r8.ir.code.ArrayLength; import com.android.tools.r8.ir.code.ArrayPut; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.CanonicalPositions; import com.android.tools.r8.ir.code.CatchHandlers; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.Cmp; @@ -116,7 +122,36 @@ public static IRCode translate( LirCode lirCode, LirDecodingStrategy strategy, AppView appView) { - Parser parser = new Parser<>(lirCode, method.getReference(), appView, strategy); + return translate( + method, + lirCode, + strategy, + appView, + new NumberGenerator(), + null, + RewrittenPrototypeDescription.none(), + appView.graphLens().getOriginalMethodSignature(method.getReference())); + } + + public static IRCode translate( + ProgramMethod method, + LirCode lirCode, + LirDecodingStrategy strategy, + AppView appView, + NumberGenerator valueNumberGenerator, + Position callerPosition, + RewrittenPrototypeDescription protoChanges, + DexMethod originalMethod) { + Parser parser = + new Parser<>( + lirCode, + originalMethod, + method.getDefinition().isD8R8Synthesized(), + appView, + strategy, + valueNumberGenerator, + callerPosition, + protoChanges); parser.parseArguments(method); parser.ensureDebugInfo(); lirCode.forEach(view -> view.accept(parser)); @@ -134,8 +169,9 @@ private static class Parser extends LirParsedInstructionCallback { private final AppView appView; private final LirCode code; private final LirDecodingStrategy strategy; - private final NumberGenerator valueNumberGenerator = new NumberGenerator(); + private final NumberGenerator valueNumberGenerator; private final NumberGenerator basicBlockNumberGenerator = new NumberGenerator(); + private final RewrittenPrototypeDescription protoChanges; private final Int2ReferenceMap blocks = new Int2ReferenceOpenHashMap<>(); @@ -145,18 +181,62 @@ private static class Parser extends LirParsedInstructionCallback { private Position currentPosition; private PositionEntry nextPositionEntry = null; private int nextIndexInPositionsTable = 0; + private final PositionEntry[] positionTable; + + private final boolean buildForInlining; public Parser( LirCode code, DexMethod method, + boolean isD8R8Synthesized, AppView appView, - LirDecodingStrategy strategy) { + LirDecodingStrategy strategy, + NumberGenerator valueNumberGenerator, + Position callerPosition, + RewrittenPrototypeDescription protoChanges) { super(code); this.appView = appView; this.code = code; this.strategy = strategy; - // Recreate the preamble position. This is active for arguments and code with no positions. - currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build(); + this.valueNumberGenerator = valueNumberGenerator; + this.protoChanges = protoChanges; + assert protoChanges != null; + if (callerPosition == null) { + buildForInlining = false; + positionTable = code.getPositionTable(); + // Recreate the preamble position. This is active for arguments and code with no positions. + currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build(); + } else { + buildForInlining = true; + PositionEntry[] inlineePositions = code.getPositionTable(); + Position inlineePreamble = null; + if (inlineePositions.length > 0 && inlineePositions[0].fromInstructionIndex == 0) { + inlineePreamble = inlineePositions[0].position; + } + CanonicalPositions canonicalPositions = + new CanonicalPositions( + callerPosition, + inlineePositions.length, + method, + isD8R8Synthesized, + inlineePreamble); + currentPosition = canonicalPositions.getPreamblePosition(); + positionTable = new PositionEntry[inlineePositions.length]; + for (int i = 0; i < inlineePositions.length; i++) { + PositionEntry inlineeEntry = inlineePositions[i]; + Position inlineePosition = inlineeEntry.position; + positionTable[i] = + new PositionEntry( + inlineeEntry.fromInstructionIndex, + canonicalPositions.getCanonical( + inlineePosition + .builderWithCopy() + .setCallerPosition( + canonicalPositions.canonicalizeCallerPosition( + inlineePosition.getCallerPosition())) + .build())); + } + } } @Override @@ -197,23 +277,47 @@ private void ensureCurrentPosition() { private void advanceNextPositionEntry() { nextPositionEntry = - nextIndexInPositionsTable < code.getPositionTable().length - ? code.getPositionTable()[nextIndexInPositionsTable++] + nextIndexInPositionsTable < positionTable.length + ? positionTable[nextIndexInPositionsTable++] : null; } public void parseArguments(ProgramMethod method) { + ArgumentInfoCollection argumentsInfo = protoChanges.getArgumentInfoCollection(); currentBlock = getBasicBlock(ENTRY_BLOCK_INDEX); boolean hasReceiverArgument = !method.getDefinition().isStatic(); - assert code.getArgumentCount() - == method.getParameters().size() + (hasReceiverArgument ? 1 : 0); + + int index = 0; if (hasReceiverArgument) { + assert argumentsInfo.getNewArgumentIndex(0) == 0; addThisArgument(method.getHolderType()); + index++; } - int index = hasReceiverArgument ? 1 : 0; - for (DexType parameter : method.getParameters()) { - addArgument(parameter, index++); + + int originalNumberOfArguments = + method.getParameters().size() + + argumentsInfo.numberOfRemovedArguments() + + method.getDefinition().getFirstNonReceiverArgumentIndex() + - protoChanges.numberOfExtraParameters(); + + int numberOfRemovedArguments = 0; + while (index < originalNumberOfArguments) { + ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(index); + if (argumentInfo.isRemovedArgumentInfo()) { + RemovedArgumentInfo removedArgumentInfo = argumentInfo.asRemovedArgumentInfo(); + addArgument(removedArgumentInfo.getType(), index++); + numberOfRemovedArguments++; + } else if (argumentInfo.isRewrittenTypeInfo()) { + RewrittenTypeInfo rewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo(); + int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments); + assert method.getArgumentType(newArgumentIndex) == rewrittenTypeInfo.getNewType(); + addArgument(rewrittenTypeInfo.getOldType(), index++); + } else { + int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments); + addArgument(method.getArgumentType(newArgumentIndex), index++); + } } + // Set up position state after adding arguments. advanceNextPositionEntry(); } @@ -252,8 +356,14 @@ public IRCode getIRCode(ProgramMethod method) { } } } - for (int i = 0; i < peekNextInstructionIndex(); ++i) { - valueNumberGenerator.next(); + if (!buildForInlining) { + // The decoding strategy will increment this on demand when built for inlining. + // Not incrementing for normal building results in nice order of instruction index and + // value number. + int lastValueIndex = getCurrentValueIndex(); + for (int i = 0; i < lastValueIndex; ++i) { + valueNumberGenerator.next(); + } } return new IRCode( appView.options(), @@ -262,7 +372,7 @@ public IRCode getIRCode(ProgramMethod method) { blockList, valueNumberGenerator, basicBlockNumberGenerator, - code.getMetadata(), + code.getMetadataForIR(), method.getOrigin(), new MutableMethodConversionOptions(appView.options())); } @@ -365,7 +475,7 @@ private Argument addArgument(DexType type, int index) { index, typeElement, code::getDebugLocalInfo); Argument argument = new Argument(dest, index, type.isBooleanType()); assert currentBlock != null; - assert currentPosition.isSyntheticPosition(); + assert currentPosition.isSyntheticPosition() || buildForInlining; argument.setPosition(currentPosition); currentBlock.getInstructions().add(argument); argument.setBlock(currentBlock); @@ -512,7 +622,7 @@ public void onDexItemBasedConstString( public void onConstClass(DexType type, boolean ignoreCompatRules) { Value dest = getOutValueForNextInstruction( - type.toTypeElement(appView, Nullability.definitelyNotNull())); + TypeElement.classClassType(appView, Nullability.definitelyNotNull())); addInstruction(new ConstClass(dest, type, ignoreCompatRules)); } @@ -727,8 +837,12 @@ public void onReturnVoid() { @Override public void onReturn(EV value) { - addInstruction(new Return(getValue(value))); - closeCurrentBlock(); + if (protoChanges.hasBeenChangedToReturnVoid()) { + onReturnVoid(); + } else { + addInstruction(new Return(getValue(value))); + closeCurrentBlock(); + } } @Override diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java index f6ca030e0d..8c8e4c5e2a 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java +++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java @@ -37,6 +37,7 @@ import com.android.tools.r8.lightir.LirCode.PositionEntry; import com.android.tools.r8.lightir.LirCode.TryCatchTable; import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -60,6 +61,7 @@ public class LirBuilder { private static final long DOUBLE_0 = Double.doubleToRawLongBits(0); private static final long DOUBLE_1 = Double.doubleToRawLongBits(1); + private final boolean useDexEstimationStrategy; private final DexItemFactory factory; private final ByteArrayWriter byteWriter = new ByteArrayWriter(); private final LirWriter writer = new LirWriter(byteWriter); @@ -140,8 +142,10 @@ public RecordFieldValuesPayload(DexField[] fields) { } } - public LirBuilder(DexMethod method, LirEncodingStrategy strategy, DexItemFactory factory) { - this.factory = factory; + public LirBuilder( + DexMethod method, LirEncodingStrategy strategy, InternalOptions options) { + useDexEstimationStrategy = options.isGeneratingDex(); + factory = options.dexItemFactory(); constants = new Reference2IntOpenHashMap<>(); positionTable = new ArrayList<>(); this.strategy = strategy; @@ -174,7 +178,9 @@ public void addTryCatchHanders(int blockIndex, CatchHandlers handlers) public LirBuilder setCurrentPosition(Position position) { assert position != null; - currentPosition = position; + if (!position.isNone()) { + currentPosition = position; + } return this; } @@ -721,7 +727,8 @@ public LirCode build() { instructionCount, tryCatchTable, debugTable, - strategy.getStrategyInfo()); + strategy.getStrategyInfo(), + useDexEstimationStrategy); } private int getCmpOpcode(NumericType type, Cmp.Bias bias) { diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java index f1c04ec074..75a1aa3967 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirCode.java +++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java @@ -3,20 +3,43 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.lightir; +import com.android.tools.r8.dex.code.CfOrDexInstruction; +import com.android.tools.r8.errors.Unimplemented; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ArgumentUse; +import com.android.tools.r8.graph.ClasspathMethod; +import com.android.tools.r8.graph.Code; import com.android.tools.r8.graph.DebugLocalInfo; +import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItem; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata; +import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata; +import com.android.tools.r8.graph.lens.GraphLens; +import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.code.CatchHandlers; +import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.IRMetadata; +import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Position; +import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.RetracerForCodePrinting; import com.google.common.collect.ImmutableMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.Consumer; -public class LirCode implements Iterable { +public class LirCode extends Code implements Iterable { public static class PositionEntry { final int fromInstructionIndex; @@ -72,7 +95,9 @@ public void forEachLocalDefinition(BiConsumer fn) { private final LirStrategyInfo strategyInfo; - private final IRMetadata metadata; + private final boolean useDexEstimationStrategy; + + private final IRMetadata irMetadata; /** Constant pool of items. */ private final DexItem[] constants; @@ -95,13 +120,13 @@ public void forEachLocalDefinition(BiConsumer fn) { private final DebugLocalInfoTable debugLocalInfoTable; public static LirBuilder builder( - DexMethod method, LirEncodingStrategy strategy, DexItemFactory factory) { - return new LirBuilder<>(method, strategy, factory); + DexMethod method, LirEncodingStrategy strategy, InternalOptions options) { + return new LirBuilder<>(method, strategy, options); } /** Should be constructed using {@link LirBuilder}. */ LirCode( - IRMetadata metadata, + IRMetadata irMetadata, DexItem[] constants, PositionEntry[] positions, int argumentCount, @@ -109,8 +134,9 @@ public static LirBuilder builder( int instructionCount, TryCatchTable tryCatchTable, DebugLocalInfoTable debugLocalInfoTable, - LirStrategyInfo strategyInfo) { - this.metadata = metadata; + LirStrategyInfo strategyInfo, + boolean useDexEstimationStrategy) { + this.irMetadata = irMetadata; this.constants = constants; this.positionTable = positions; this.argumentCount = argumentCount; @@ -119,6 +145,24 @@ public static LirBuilder builder( this.tryCatchTable = tryCatchTable; this.debugLocalInfoTable = debugLocalInfoTable; this.strategyInfo = strategyInfo; + this.useDexEstimationStrategy = useDexEstimationStrategy; + } + + @SuppressWarnings("unchecked") + @Override + public LirCode asLirCode() { + // TODO(b/225838009): Unchecked cast will be removed once the encoding strategy is definitive. + return (LirCode) this; + } + + @Override + protected int computeHashCode() { + throw new Unreachable("LIR code should not be subject to hashing."); + } + + @Override + protected boolean computeEquals(Object other) { + throw new Unreachable("LIR code should not be subject to equality checks."); } public EV decodeValueIndex(int encodedValueIndex, int currentValueIndex) { @@ -143,8 +187,8 @@ public int getInstructionCount() { return instructionCount; } - public IRMetadata getMetadata() { - return metadata; + public IRMetadata getMetadataForIR() { + return irMetadata; } public DexItem getConstantItem(int index) { @@ -171,13 +215,185 @@ public int[] getDebugLocalEnds(int instructionValueIndex) { return debugLocalInfoTable == null ? null : debugLocalInfoTable.getEnds(instructionValueIndex); } + @Override + public BytecodeMetadata getMetadata() { + // Bytecode metadata is recomputed when finalizing via IR. + throw new Unreachable(); + } + + @Override + public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) { + // Bytecode metadata is recomputed when finalizing via IR. + throw new Unreachable(); + } + @Override public LirIterator iterator() { return new LirIterator(new ByteArrayIterator(instructions)); } + @Override + public IRCode buildIR( + ProgramMethod method, + AppView appView, + Origin origin, + MutableMethodConversionOptions conversionOptions) { + LirCode typedLir = asLirCode(); + return Lir2IRConverter.translate( + method, + typedLir, + LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, null), + appView); + } + + @Override + public IRCode buildInliningIR( + ProgramMethod context, + ProgramMethod method, + AppView appView, + GraphLens codeLens, + NumberGenerator valueNumberGenerator, + Position callerPosition, + Origin origin, + RewrittenPrototypeDescription protoChanges) { + assert valueNumberGenerator != null; + assert callerPosition != null; + assert protoChanges != null; + LirCode typedLir = asLirCode(); + IRCode irCode = + Lir2IRConverter.translate( + method, + typedLir, + LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, valueNumberGenerator), + appView, + valueNumberGenerator, + callerPosition, + protoChanges, + appView.graphLens().getOriginalMethodSignature(method.getReference())); + // TODO(b/225838009): Should we keep track of which code objects need to be narrowed? + // In particular, the encoding of phis does not maintain interfaces. + new TypeAnalysis(appView).narrowing(irCode); + return irCode; + } + + @Override + public void registerCodeReferences(ProgramMethod method, UseRegistry registry) { + assert registry.getTraversalContinuation().shouldContinue(); + LirUseRegistryCallback registryCallbacks = new LirUseRegistryCallback<>(this, registry); + for (LirInstructionView view : this) { + registryCallbacks.onInstructionView(view); + if (registry.getTraversalContinuation().shouldBreak()) { + return; + } + } + if (tryCatchTable != null) { + for (CatchHandlers handler : tryCatchTable.tryCatchHandlers.values()) { + for (DexType guard : handler.getGuards()) { + registry.registerExceptionGuard(guard); + if (registry.getTraversalContinuation().shouldBreak()) { + return; + } + } + } + } + } + + @Override + public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) { + throw new Unimplemented(); + } + + @Override + public Int2ReferenceMap collectParameterInfo( + DexEncodedMethod encodedMethod, AppView appView) { + throw new Unimplemented(); + } + + @Override + public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) { + throw new Unimplemented(); + } + @Override public String toString() { return new LirPrinter<>(this).prettyPrint(); } + + @Override + public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) { + // TODO(b/225838009): Add retracing to printer. + return toString(); + } + + @Override + public int estimatedDexCodeSizeUpperBoundInBytes() { + throw new Unimplemented(); + } + + @Override + public int estimatedSizeForInlining() { + if (useDexEstimationStrategy) { + LirSizeEstimation estimation = new LirSizeEstimation<>(this); + for (LirInstructionView view : this) { + estimation.onInstructionView(view); + } + return estimation.getSizeEstimate(); + } else { + // TODO(b/225838009): Currently the size estimation for CF has size one for each instruction + // (even switches!) and ignores stack instructions, thus loads to arguments are not included. + // The result is a much smaller estimate than for DEX. Once LIR is in place we should use the + // same estimate for both. + return instructionCount; + } + } + + @Override + public boolean estimatedSizeForInliningAtMost(int threshold) { + if (useDexEstimationStrategy) { + LirSizeEstimation estimation = new LirSizeEstimation<>(this); + for (LirInstructionView view : this) { + estimation.onInstructionView(view); + if (estimation.getSizeEstimate() > threshold) { + return false; + } + } + return true; + } else { + return estimatedSizeForInlining() <= threshold; + } + } + + @Override + public Code getCodeAsInlining(DexMethod caller, DexEncodedMethod callee, DexItemFactory factory) { + throw new Unimplemented(); + } + + @Override + public boolean isEmptyVoidMethod() { + for (LirInstructionView view : this) { + int opcode = view.getOpcode(); + if (opcode != LirOpcodes.RETURN && opcode != LirOpcodes.DEBUGPOS) { + return false; + } + } + return true; + } + + @Override + public boolean hasMonitorInstructions() { + for (LirInstructionView view : this) { + int opcode = view.getOpcode(); + if (opcode == LirOpcodes.MONITORENTER || opcode == LirOpcodes.MONITOREXIT) { + return true; + } + } + return false; + } + + @Override + public void forEachPosition(Consumer positionConsumer) { + for (PositionEntry entry : positionTable) { + positionConsumer.accept(entry.position); + } + } } diff --git a/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java b/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java index 0b0a51d29a..f0eaeef548 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java +++ b/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java @@ -6,6 +6,7 @@ import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Phi; import java.util.function.Function; import java.util.function.IntFunction; @@ -13,6 +14,16 @@ /** Abstraction for how to decode SSA values (and basic blocks) when reading LIR. */ public abstract class LirDecodingStrategy { + private final NumberGenerator valueNumberGenerator; + + public LirDecodingStrategy(NumberGenerator valueNumberGenerator) { + this.valueNumberGenerator = valueNumberGenerator; + } + + public final int getValueNumber(int encodedValueIndex) { + return valueNumberGenerator == null ? encodedValueIndex : valueNumberGenerator.next(); + } + public abstract V getValue(EV encodedValue, LirStrategyInfo strategyInfo); public abstract V getValueDefinitionForInstructionIndex( diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java index 6803d0b58c..fc1ccc60be 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java +++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java @@ -151,7 +151,7 @@ static boolean isOneByteInstruction(int opcode) { // int JSR = 168; // int RET = 169; int TABLESWITCH = 170; - int LOOKUPSWITCH = 171; + // int LOOKUPSWITCH = 171; // int IRETURN = 172; // int LRETURN = 173; // int FRETURN = 174; @@ -447,8 +447,7 @@ static String toString(int opcode) { // case RET: return "RET"; case TABLESWITCH: return "TABLESWITCH"; - case LOOKUPSWITCH: - return "LOOKUPSWITCH"; + // case LOOKUPSWITCH: case ARETURN: return "ARETURN"; case RETURN: diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java index 444df4e20c..d979ada90f 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java +++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java @@ -448,13 +448,21 @@ public void onStaticPut(DexField field, EV value) { onFieldInstruction(field); } - public abstract void onInstanceGet(DexField field, EV object); + public void onInstanceGet(DexField field, EV object) { + onFieldInstruction(field); + } - public abstract void onInstancePut(DexField field, EV object, EV value); + public void onInstancePut(DexField field, EV object, EV value) { + onFieldInstruction(field); + } - public abstract void onNewArrayEmpty(DexType type, EV size); + public void onNewArrayEmpty(DexType type, EV size) { + onInstruction(); + } - public abstract void onThrow(EV exception); + public void onThrow(EV exception) { + onInstruction(); + } public void onReturnVoid() { onInstruction(); @@ -493,9 +501,17 @@ public void onCmpInstruction(int opcode, EV leftValue, EV rightValue) { onInstruction(); } - public abstract void onMonitorEnter(EV value); + public void onMonitorInstruction(EV value) { + onInstruction(); + } + + public void onMonitorEnter(EV value) { + onMonitorInstruction(value); + } - public abstract void onMonitorExit(EV value); + public void onMonitorExit(EV value) { + onMonitorInstruction(value); + } public void onNewUnboxedEnumInstance(DexType type, int ordinal) { onInstruction(); diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java index 96d9cef7da..cfe1a083bd 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java +++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java @@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.MemberType; import com.android.tools.r8.ir.code.NumericType; @@ -61,7 +62,7 @@ private String fmtValueIndex(int valueIndex) { } private String fmtValueIndex(EV valueIndex) { - return valueIndex.toString(); + return "v" + valueIndex.toString(); } private String fmtInsnIndex(int instructionIndex) { @@ -86,6 +87,22 @@ public String prettyPrint() { advanceToNextValueIndex(); } code.forEach(this::onInstructionView); + if (code.getTryCatchTable() != null) { + builder.append("try-catch-handlers:\n"); + code.getTryCatchTable() + .tryCatchHandlers + .forEach( + (index, handlers) -> { + builder.append(index).append(":\n"); + for (CatchHandler handler : handlers) { + builder + .append(handler.getGuard()) + .append(" -> ") + .append(handler.getTarget()) + .append('\n'); + } + }); + } return builder.toString(); } diff --git a/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java new file mode 100644 index 0000000000..eaa93d0698 --- /dev/null +++ b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java @@ -0,0 +1,339 @@ +// Copyright (c) 2023, 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.lightir; + +import com.android.tools.r8.dex.code.DexAget; +import com.android.tools.r8.dex.code.DexAput; +import com.android.tools.r8.dex.code.DexArrayLength; +import com.android.tools.r8.dex.code.DexBase1Format; +import com.android.tools.r8.dex.code.DexBase2Format; +import com.android.tools.r8.dex.code.DexBase3Format; +import com.android.tools.r8.dex.code.DexCheckCast; +import com.android.tools.r8.dex.code.DexConst16; +import com.android.tools.r8.dex.code.DexConst4; +import com.android.tools.r8.dex.code.DexConstClass; +import com.android.tools.r8.dex.code.DexConstString; +import com.android.tools.r8.dex.code.DexConstWide16; +import com.android.tools.r8.dex.code.DexFillArrayData; +import com.android.tools.r8.dex.code.DexFillArrayDataPayload; +import com.android.tools.r8.dex.code.DexFilledNewArray; +import com.android.tools.r8.dex.code.DexGoto; +import com.android.tools.r8.dex.code.DexInstanceOf; +import com.android.tools.r8.dex.code.DexInvokeCustom; +import com.android.tools.r8.dex.code.DexMonitorEnter; +import com.android.tools.r8.dex.code.DexMonitorExit; +import com.android.tools.r8.dex.code.DexMove; +import com.android.tools.r8.dex.code.DexMoveException; +import com.android.tools.r8.dex.code.DexNewArray; +import com.android.tools.r8.dex.code.DexNewInstance; +import com.android.tools.r8.dex.code.DexNotInt; +import com.android.tools.r8.dex.code.DexNotLong; +import com.android.tools.r8.dex.code.DexPackedSwitch; +import com.android.tools.r8.dex.code.DexPackedSwitchPayload; +import com.android.tools.r8.dex.code.DexSget; +import com.android.tools.r8.dex.code.DexThrow; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload; + +public class LirSizeEstimation extends LirParsedInstructionCallback implements LirOpcodes { + + private int sizeEstimate = 0; + + LirSizeEstimation(LirCode code) { + super(code); + } + + @Override + public int getCurrentValueIndex() { + // We don't use value information. + return 0; + } + + public int getSizeEstimate() { + return sizeEstimate; + } + + /** + * Most size information can be found just using opcode. + * + *

We overwrite the base view callback and only in the few payload instruction cases do we make + * use of the parsed-instruction callbacks. + */ + @Override + public void onInstructionView(LirInstructionView view) { + sizeEstimate += instructionSize(view.getOpcode(), view); + } + + @Override + public void onIntSwitch(EV unusedValue, IntSwitchPayload payload) { + sizeEstimate += + DexPackedSwitch.SIZE + + DexPackedSwitchPayload.SIZE + + (2 * payload.keys.length) + + (2 * payload.targets.length); + } + + @Override + public void onNewArrayFilledData(int elementWidth, long size, short[] data, EV unusedSrc) { + sizeEstimate += DexFillArrayData.SIZE + DexFillArrayDataPayload.SIZE + 4 + data.length; + } + + private int instructionSize(int opcode, LirInstructionView view) { + switch (opcode) { + case TABLESWITCH: + case NEWARRAYFILLEDDATA: + // The payload instructions use the "parsed callback" to compute the payloads. + super.onInstructionView(view); + // The full size is added by the callbacks so return zero here. + return 0; + + case ACONST_NULL: + case ICONST_M1: + case ICONST_0: + case ICONST_1: + case ICONST_2: + case ICONST_3: + case ICONST_4: + case ICONST_5: + return DexConst4.SIZE; + + case LCONST_0: + case LCONST_1: + case FCONST_0: + case FCONST_1: + case FCONST_2: + case DCONST_0: + case DCONST_1: + return DexConstWide16.SIZE; + + case LDC: + // Most of the const loads are the same size (2). + return DexConstString.SIZE; + + case IALOAD: + case LALOAD: + case FALOAD: + case DALOAD: + case AALOAD: + case BALOAD: + case CALOAD: + case SALOAD: + // The loads are all size 2. + return DexAget.SIZE; + + case IASTORE: + case LASTORE: + case FASTORE: + case DASTORE: + case AASTORE: + case BASTORE: + case CASTORE: + case SASTORE: + // The loads are all size 2. + return DexAput.SIZE; + + case IADD: + case LADD: + case FADD: + case DADD: + case ISUB: + case LSUB: + case FSUB: + case DSUB: + case IMUL: + case LMUL: + case FMUL: + case DMUL: + case IDIV: + case LDIV: + case FDIV: + case DDIV: + case IREM: + case LREM: + case FREM: + case DREM: + // The binary ops are all size 2. + return DexBase2Format.SIZE; + + case INEG: + case LNEG: + case FNEG: + case DNEG: + // The negs are all size 1. + return DexBase1Format.SIZE; + + case ISHL: + case LSHL: + case ISHR: + case LSHR: + case IUSHR: + case LUSHR: + case IAND: + case LAND: + case IOR: + case LOR: + case IXOR: + case LXOR: + // The binary ops are all size 2. + return DexBase2Format.SIZE; + + case I2L: + case I2F: + case I2D: + case L2I: + case L2F: + case L2D: + case F2I: + case F2L: + case F2D: + case D2I: + case D2L: + case D2F: + case I2B: + case I2C: + case I2S: + // Number conversions are all size 1. + return DexBase1Format.SIZE; + + case LCMP: + case FCMPL: + case FCMPG: + case DCMPL: + case DCMPG: + return DexBase2Format.SIZE; + + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ACMPEQ: + case IF_ACMPNE: + return DexBase2Format.SIZE; + + case GOTO: + return DexGoto.SIZE; + + case ARETURN: + case RETURN: + return DexBase1Format.SIZE; + + case GETSTATIC: + case PUTSTATIC: + case GETFIELD: + case PUTFIELD: + return DexBase2Format.SIZE; + + case INVOKEVIRTUAL: + case INVOKESPECIAL: + case INVOKESTATIC: + case INVOKEINTERFACE: + return DexBase3Format.SIZE; + + case INVOKEDYNAMIC: + return DexInvokeCustom.SIZE; + + case NEW: + return DexNewInstance.SIZE; + case NEWARRAY: + return DexNewArray.SIZE; + case ARRAYLENGTH: + return DexArrayLength.SIZE; + case ATHROW: + return DexThrow.SIZE; + case CHECKCAST: + return DexCheckCast.SIZE; + case INSTANCEOF: + return DexInstanceOf.SIZE; + case MONITORENTER: + return DexMonitorEnter.SIZE; + case MONITOREXIT: + return DexMonitorExit.SIZE; + case MULTIANEWARRAY: + return DexFilledNewArray.SIZE; + + case IFNULL: + case IFNONNULL: + return DexBase1Format.SIZE; + + // Non-CF instructions. + case ICONST: + case LCONST: + case FCONST: + case DCONST: + return DexConst16.SIZE; + + case INVOKESTATIC_ITF: + case INVOKEDIRECT: + case INVOKEDIRECT_ITF: + case INVOKESUPER: + case INVOKESUPER_ITF: + return DexBase3Format.SIZE; + + case DEBUGPOS: + // Often debug positions will be associated with instructions so assume size 0. + return 0; + + case PHI: + // Assume a move per phi. + return DexMove.SIZE; + + case FALLTHROUGH: + // Hopefully fallthrough points will not materialize as instructions. + return 0; + + case MOVEEXCEPTION: + return DexMoveException.SIZE; + + case DEBUGLOCALWRITE: + return DexMove.SIZE; + + case INVOKENEWARRAY: + return DexFilledNewArray.SIZE; + + case ITEMBASEDCONSTSTRING: + return DexConstString.SIZE; + + case NEWUNBOXEDENUMINSTANCE: + return DexConst16.SIZE; + + case INOT: + return DexNotInt.SIZE; + case LNOT: + return DexNotLong.SIZE; + + case DEBUGLOCALREAD: + // These reads do not materialize after register allocation. + return 0; + + case INITCLASS: + return DexSget.SIZE; + + case INVOKEPOLYMORPHIC: + return DexBase3Format.SIZE; + + case RECORDFIELDVALUES: + // Rewritten to new-array in DEX. + return DexNewArray.SIZE; + + case CHECKCAST_SAFE: + case CHECKCAST_IGNORE_COMPAT: + return DexCheckCast.SIZE; + + case CONSTCLASS_IGNORE_COMPAT: + return DexConstClass.SIZE; + + default: + throw new Unreachable("Unexpected LIR opcode: " + opcode); + } + } +} diff --git a/src/main/java/com/android/tools/r8/lightir/LirStrategy.java b/src/main/java/com/android/tools/r8/lightir/LirStrategy.java index 108586f795..07ac579740 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirStrategy.java +++ b/src/main/java/com/android/tools/r8/lightir/LirStrategy.java @@ -8,6 +8,7 @@ import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Phi.RegisterReadType; import com.android.tools.r8.ir.code.Value; @@ -29,9 +30,15 @@ * the possible encoding for phi and non-phi values. */ public abstract class LirStrategy { + + public static LirStrategy getDefaultStrategy() { + return new PhiInInstructionsStrategy(); + } + public abstract LirEncodingStrategy getEncodingStrategy(); - public abstract LirDecodingStrategy getDecodingStrategy(LirCode code); + public abstract LirDecodingStrategy getDecodingStrategy( + LirCode code, NumberGenerator valueNumberGenerator); /** * Encoding of a value with a phi-bit. @@ -139,8 +146,9 @@ public LirEncodingStrategy getEncodingStrategy() { } @Override - public LirDecodingStrategy getDecodingStrategy(LirCode code) { - return new DecodingStrategy(code); + public LirDecodingStrategy getDecodingStrategy( + LirCode code, NumberGenerator valueNumberGenerator) { + return new DecodingStrategy(code, valueNumberGenerator); } private static class StrategyInfo extends LirStrategyInfo { @@ -234,7 +242,8 @@ private static class DecodingStrategy extends LirDecodingStrategy code) { + DecodingStrategy(LirCode code, NumberGenerator valueNumberGenerator) { + super(valueNumberGenerator); values = new Value[code.getArgumentCount() + code.getInstructionCount()]; int phiValueIndex = -1; for (LirInstructionView view : code) { @@ -270,7 +279,7 @@ public Value getValue(PhiOrValue encodedValue, LirStrategyInfo strat int index = decode(encodedValue, strategyInfo); Value value = values[index]; if (value == null) { - value = new Value(index, TypeElement.getBottom(), null); + value = new Value(getValueNumber(index), TypeElement.getBottom(), null); values[index] = value; } return value; @@ -284,7 +293,7 @@ public Value getValueDefinitionForInstructionIndex( DebugLocalInfo localInfo = getLocalInfo.apply(encodedValue); Value value = values[index]; if (value == null) { - value = new Value(index, type, localInfo); + value = new Value(getValueNumber(index), type, localInfo); values[index] = value; } else { value.setType(type); @@ -306,7 +315,8 @@ public Phi getPhiDefinitionForInstructionIndex( PhiOrValue encodedValue = getEncodedPhiForAbsoluteValueIndex(valueIndex, strategyInfo); BasicBlock block = getBlock.apply(encodedValue.getBlockIndex()); DebugLocalInfo localInfo = getLocalInfo.apply(encodedValue); - Phi phi = new Phi(valueIndex, block, type, localInfo, RegisterReadType.NORMAL); + Phi phi = + new Phi(getValueNumber(valueIndex), block, type, localInfo, RegisterReadType.NORMAL); Value value = values[valueIndex]; if (value != null) { // A fake ssa value has already been created, replace the users by the actual phi. diff --git a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java new file mode 100644 index 0000000000..67f7a4ec1e --- /dev/null +++ b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java @@ -0,0 +1,153 @@ +// Copyright (c) 2023, 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.lightir; + +import com.android.tools.r8.graph.DexCallSite; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexMethodHandle; +import com.android.tools.r8.graph.DexProto; +import com.android.tools.r8.graph.DexReference; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.graph.UseRegistry.MethodHandleUse; +import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo; +import java.util.List; + +public class LirUseRegistryCallback extends LirParsedInstructionCallback { + + private final UseRegistry registry; + + public LirUseRegistryCallback(LirCode code, UseRegistry registry) { + super(code); + this.registry = registry; + } + + @Override + public int getCurrentValueIndex() { + // The registry of instructions does not require knowledge of value indexes. + return 0; + } + + @Override + public void onCheckCast(DexType type, EV value, boolean ignoreCompatRules) { + registry.registerCheckCast(type, ignoreCompatRules); + } + + @Override + public void onSafeCheckCast(DexType type, EV value) { + registry.registerSafeCheckCast(type); + } + + @SuppressWarnings("unchecked") + @Override + public void onConstClass(DexType type, boolean ignoreCompatRules) { + registry.registerConstClass(type, null, ignoreCompatRules); + } + + @Override + public void onConstMethodHandle(DexMethodHandle methodHandle) { + registry.registerMethodHandle(methodHandle, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY); + } + + @Override + public void onConstMethodType(DexProto methodType) { + registry.registerProto(methodType); + } + + @Override + public void onDexItemBasedConstString( + DexReference item, NameComputationInfo nameComputationInfo) { + if (nameComputationInfo.needsToRegisterReference()) { + assert item.isDexType(); + registry.registerTypeReference(item.asDexType()); + } + } + + @Override + public void onInitClass(DexType clazz) { + registry.registerInitClass(clazz); + } + + @Override + public void onInstanceGet(DexField field, EV object) { + registry.registerInstanceFieldRead(field); + } + + @Override + public void onInstancePut(DexField field, EV object, EV value) { + registry.registerInstanceFieldWrite(field); + } + + @Override + public void onStaticGet(DexField field) { + registry.registerStaticFieldRead(field); + } + + @Override + public void onStaticPut(DexField field, EV value) { + registry.registerStaticFieldWrite(field); + } + + @Override + public void onInstanceOf(DexType type, EV value) { + registry.registerInstanceOf(type); + } + + @Override + public void onInvokeCustom(DexCallSite callSite, List arguments) { + registry.registerCallSite(callSite); + } + + @Override + public void onInvokeDirect(DexMethod method, List arguments, boolean isInterface) { + registry.registerInvokeDirect(method); + } + + @Override + public void onInvokeInterface(DexMethod method, List arguments) { + registry.registerInvokeInterface(method); + } + + @Override + public void onInvokeStatic(DexMethod method, List arguments, boolean isInterface) { + registry.registerInvokeStatic(method); + } + + @Override + public void onInvokeSuper(DexMethod method, List arguments, boolean isInterface) { + registry.registerInvokeSuper(method); + } + + @Override + public void onInvokeVirtual(DexMethod method, List arguments) { + registry.registerInvokeVirtual(method); + } + + @Override + public void onInvokeMultiNewArray(DexType type, List arguments) { + registry.registerTypeReference(type); + } + + @Override + public void onInvokeNewArray(DexType type, List arguments) { + registry.registerTypeReference(type); + } + + @Override + public void onNewArrayEmpty(DexType type, EV size) { + registry.registerTypeReference(type); + } + + @Override + public void onNewInstance(DexType clazz) { + registry.registerNewInstance(clazz); + } + + @Override + public void onNewUnboxedEnumInstance(DexType type, int ordinal) { + registry.registerNewUnboxedEnumInstance(type); + } +} diff --git a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java index 0428126ddd..6e9366f9d1 100644 --- a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java +++ b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java @@ -6,6 +6,7 @@ import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Phi.RegisterReadType; import com.android.tools.r8.ir.code.Value; @@ -23,8 +24,9 @@ public LirEncodingStrategy getEncodingStrategy() { } @Override - public LirDecodingStrategy getDecodingStrategy(LirCode code) { - return new DecodingStrategy(code); + public LirDecodingStrategy getDecodingStrategy( + LirCode code, NumberGenerator valueNumberGenerator) { + return new DecodingStrategy(code, valueNumberGenerator); } private static class EncodingStrategy extends LirEncodingStrategy { @@ -94,7 +96,8 @@ private static class DecodingStrategy extends LirDecodingStrategy code) { + DecodingStrategy(LirCode code, NumberGenerator valueNumberGenerator) { + super(valueNumberGenerator); values = new Value[code.getArgumentCount() + code.getInstructionCount()]; } @@ -103,7 +106,7 @@ public Value getValue(Integer encodedValue, LirStrategyInfo strategyInf int index = encodedValue; Value value = values[index]; if (value == null) { - value = new Value(index, TypeElement.getBottom(), null); + value = new Value(getValueNumber(index), TypeElement.getBottom(), null); values[index] = value; } return value; @@ -115,7 +118,7 @@ public Value getValueDefinitionForInstructionIndex( DebugLocalInfo localInfo = getLocalInfo.apply(index); Value value = values[index]; if (value == null) { - value = new Value(index, type, localInfo); + value = new Value(getValueNumber(index), type, localInfo); values[index] = value; } else { value.setType(type); @@ -138,7 +141,8 @@ public Phi getPhiDefinitionForInstructionIndex( LirStrategyInfo strategyInfo) { BasicBlock block = getBlock.apply(valueIndex); DebugLocalInfo localInfo = getLocalInfo.apply(valueIndex); - Phi phi = new Phi(valueIndex, block, type, localInfo, RegisterReadType.NORMAL); + Phi phi = + new Phi(getValueNumber(valueIndex), block, type, localInfo, RegisterReadType.NORMAL); Value value = values[valueIndex]; if (value != null) { // A fake ssa value has already been created, replace the users by the actual phi. 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 6259f3a7b2..9afb2e9b39 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -2077,10 +2077,24 @@ public boolean shouldApplyInliningToInlinee( public static class TestingOptions { + public boolean roundtripThroughLir = false; + private boolean useLir = false; + + public void enableLir() { + useLir = true; + } + + public void disableLir() { + useLir = false; + } + + public boolean canUseLir(AppView appView) { + return useLir && appView.enableWholeProgramOptimizations(); + } + // If false, use the desugared library implementation when desugared library is enabled. public boolean alwaysBackportListSetMapMethods = true; public boolean neverReuseCfLocalRegisters = false; - public boolean roundtripThroughLir = false; public boolean checkReceiverAlwaysNullInCallSiteOptimization = true; public boolean forceInlineAPIConversions = false; public boolean ignoreValueNumbering = false; diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java index 22f879db09..fa1d5797c7 100644 --- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java +++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java @@ -132,10 +132,11 @@ public void lambdaDesugaring() throws Throwable { .run(); test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring") + .withOptionConsumer(o -> o.testing.enableLir()) .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K)) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 1, "lambdadesugaring")) .run(); } @@ -172,9 +173,10 @@ public void lambdaDesugaringWithDefaultMethods() throws Throwable { test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring") .withMinApiLevel(AndroidApiLevel.N) + .withOptionConsumer(opts -> opts.testing.enableLir()) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 1, "lambdadesugaring")) .run(); } diff --git a/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java index f0bc1e5091..173efa19bc 100644 --- a/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java +++ b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java @@ -68,7 +68,7 @@ public void testD8() throws Exception { } private void inspect(CodeInspector inspector) { - assertEquals(parameters.isCfRuntime() ? 1 : 2, inspector.allClasses().size()); + assertEquals(1, inspector.allClasses().size()); } @Test @@ -77,6 +77,7 @@ public void testR8() throws Exception { .addProgramFiles(getProgramFiles(target)) .setMinApi(parameters) .addKeepMainRule(MAIN_CLASS) + .addOptionsModification(o -> o.testing.enableLir()) .run(parameters.getRuntime(), MAIN_CLASS) .inspect(this::inspect) .assertSuccessWithOutputLines(EXPECTED_OUTPUT); diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java index 84f50033fb..ec1f304dfa 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java @@ -172,14 +172,14 @@ private void modifyIr(IRCode irCode) { assertNotNull(argument); Value argumentValue = argument.outValue(); - BasicBlock block1 = irCode.blocks.get(1); + BasicBlock block1 = irCode.blocks.stream().filter(b -> b.getNumber() == 1).findFirst().get(); assertTrue(block1.exit().isIf()); - BasicBlock block3 = irCode.blocks.get(3); + BasicBlock block3 = irCode.blocks.stream().filter(b -> b.getNumber() == 3).findFirst().get(); assertTrue(block1.getSuccessors().contains(block3)); assertTrue(block3.exit().isGoto()); - BasicBlock block4 = irCode.blocks.get(4); + BasicBlock block4 = irCode.blocks.stream().filter(b -> b.getNumber() == 4).findFirst().get(); assertSame(block3.getUniqueNormalSuccessor(), block4); Phi firstPhi = diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java index 5ece012422..483ed337f8 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java @@ -12,7 +12,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import com.android.tools.r8.SingleTestRunResult; import com.android.tools.r8.TestParameters; @@ -42,7 +41,6 @@ import com.android.tools.r8.utils.codeinspector.FoundClassSubject; import com.google.common.collect.Sets; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; import org.junit.Assume; @@ -312,6 +310,8 @@ public void testDesugaredLambdas() throws Exception { .allowAccessModification() .enableInliningAnnotations() .addDontObfuscate() + // Using LIR changes the inlining heuristics so enable it consistently. + .addOptionsModification(o -> o.testing.enableLir()) .setMinApi(parameters) .run(parameters.getRuntime(), main) .assertSuccessWithOutput(javaOutput); @@ -324,16 +324,14 @@ public void testDesugaredLambdas() throws Exception { .filter(FoundClassSubject::isSynthesizedJavaLambdaClass) .map(FoundClassSubject::getFinalName) .collect(Collectors.toList()); + assertEquals(Collections.emptyList(), synthesizedJavaLambdaClasses); - // TODO(b/120814598): Should only be "java.lang.StringBuilder". assertEquals( - new HashSet<>(synthesizedJavaLambdaClasses), + Collections.singleton("java.lang.StringBuilder"), collectTypes(clazz.uniqueMethodWithOriginalName("testStatelessLambda"))); - assertTrue( - inspector.allClasses().stream().anyMatch(ClassSubject::isSynthesizedJavaLambdaClass)); assertEquals( - Sets.newHashSet("java.lang.StringBuilder"), + Collections.singleton("java.lang.StringBuilder"), collectTypes(clazz.uniqueMethodWithOriginalName("testStatefulLambda"))); } diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java index 8f8541c2f3..37e6c57a6a 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java @@ -39,7 +39,7 @@ public void test() throws Exception { HorizontallyMergedClassesInspector::assertNoClassesMerged) .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters) - .addOptionsModification(o -> o.testing.roundtripThroughLir = true) + .addOptionsModification(o -> o.testing.enableLir()) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines("42") .inspect(inspector -> assertThat(inspector.clazz(anim.class), isAbsent())); diff --git a/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java b/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java index abd980d00e..909c0830bb 100644 --- a/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java +++ b/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java @@ -12,12 +12,12 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.InternalOptions; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -74,10 +74,13 @@ public LirStrategyInfo getStrategyInfo() { @Test public void test() { - DexItemFactory factory = new DexItemFactory(); - DexMethod method = factory.createMethod(Reference.methodFromDescriptor("LFoo;", "bar", "()V")); + InternalOptions options = new InternalOptions(); + DexMethod method = + options + .dexItemFactory() + .createMethod(Reference.methodFromDescriptor("LFoo;", "bar", "()V")); LirCode code = - LirCode.builder(method, new ThrowingStrategy(), factory) + LirCode.builder(method, new ThrowingStrategy(), options) .setMetadata(IRMetadata.unknown()) .addConstNull() .addConstInt(42) diff --git a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java index c4fa84b027..a39c770285 100644 --- a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java +++ b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java @@ -4,7 +4,6 @@ package com.android.tools.r8.regress; import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.TestBase; @@ -51,6 +50,7 @@ public void testR8() throws Exception { .addKeepMainRule(TestClass.class) .addInnerClasses(Regress160394262Test.class) .setMinApi(parameters) + .addOptionsModification(o -> o.testing.enableLir()) .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(EXPECTED) .inspect(this::checkJoinerIsClassInlined); @@ -58,14 +58,7 @@ public void testR8() throws Exception { private void checkJoinerIsClassInlined(CodeInspector inspector) { assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), isAbsent()); - // TODO(b/160640028): Joiner should be class inlined. - // When line info tables are kept we appear to successfully inline Joiner. Reason unknown. - if (parameters.isCfRuntime() - || parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport())) { - assertThat(inspector.clazz(Joiner.class), isPresent()); - } else { - assertThat(inspector.clazz(Joiner.class), isAbsent()); - } + assertThat(inspector.clazz(Joiner.class), isAbsent()); } static class TestClass { From ede5cef448dd2738c5c5f688a59797d172be5717 Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Thu, 15 Jun 2023 16:52:42 +0200 Subject: [PATCH 126/153] Place shared code items together If code deduplication is enabled, sort the code items to locate the code objects together. On platform targets this has a positive impact on mapped dex code Bug: b/284336071 Change-Id: I49df2c391bfa8f9b77a5619d9c14f29da7d643c8 --- .../DefaultMixedSectionLayoutStrategy.java | 56 ++++++++++++- .../tools/r8/dex/DexCodeDeduppingTest.java | 78 ++++++++++++++++++- 2 files changed, 129 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java index dee0f55829..d9f6def904 100644 --- a/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java +++ b/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java @@ -15,6 +15,7 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexTypeList; import com.android.tools.r8.graph.DexWritableCode; +import com.android.tools.r8.graph.DexWritableCode.DexWritableCacheKey; import com.android.tools.r8.graph.ParameterAnnotationsList; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.naming.ClassNameMapper; @@ -24,7 +25,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class DefaultMixedSectionLayoutStrategy extends MixedSectionLayoutStrategy { @@ -67,9 +70,46 @@ public Collection getCodeLayout() { return getCodeLayoutForClasses(mixedSectionOffsets.getClassesWithData()); } + private static class DeduplicatedCodeCounts { + private Map counts; + private final AppView appView; + + private DeduplicatedCodeCounts(AppView appView) { + this.appView = appView; + } + + public void addCode(DexWritableCode dexWritableCode, ProgramMethod method) { + assert appView.options().canUseCanonicalizedCodeObjects(); + if (counts == null) { + counts = new HashMap<>(); + } + DexWritableCacheKey cacheKey = + dexWritableCode.getCacheLookupKey(method, appView.dexItemFactory()); + if (!counts.containsKey(cacheKey)) { + counts.put(cacheKey, 1); + } else { + counts.put(cacheKey, counts.get(cacheKey) + 1); + } + } + + public int getCount(ProgramMethod method) { + if (counts == null) { + assert !appView.options().canUseCanonicalizedCodeObjects() + || method.getDefinition().getDexWritableCodeOrNull() == null; + return 1; + } + DexWritableCode dexWritableCodeOrNull = method.getDefinition().getDexWritableCodeOrNull(); + DexWritableCacheKey cacheLookupKey = + dexWritableCodeOrNull.getCacheLookupKey(method, appView.dexItemFactory()); + assert counts.containsKey(cacheLookupKey); + return counts.get(cacheLookupKey); + } + } + final Collection getCodeLayoutForClasses(Collection classes) { - ProgramMethodMap codeToSignatureMap = ProgramMethodMap.create(); + ProgramMethodMap codeToDexSortingKeyMap = ProgramMethodMap.create(); List codesSorted = new ArrayList<>(); + DeduplicatedCodeCounts codeCounts = new DeduplicatedCodeCounts(appView); for (DexProgramClass clazz : classes) { clazz.forEachProgramMethodMatching( DexEncodedMethod::hasCode, @@ -77,13 +117,23 @@ final Collection getCodeLayoutForClasses(Collection defaultCodeSorting = + Comparator.comparing(codeToDexSortingKeyMap::get); + if (appView.options().canUseCanonicalizedCodeObjects()) { + codesSorted.sort( + Comparator.comparingInt(codeCounts::getCount).thenComparing(defaultCodeSorting)); + } else { + codesSorted.sort(defaultCodeSorting); + } return codesSorted; } diff --git a/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java b/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java index 4b0b57aebb..d5b8f32e82 100644 --- a/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java +++ b/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java @@ -4,6 +4,7 @@ package com.android.tools.r8.dex; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.D8TestCompileResult; @@ -15,10 +16,14 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector; import com.android.tools.r8.utils.AndroidApiLevel; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import org.junit.Test; @@ -31,6 +36,12 @@ public class DexCodeDeduppingTest extends TestBase { private final TestParameters parameters; private static final List EXPECTED = ImmutableList.of("foo", "bar", "foo", "bar"); + private static final int ONE_CLASS_COUNT = 4; + private static final int ONE_CLASS_DEDUPLICATED_COUNT = 3; + + private static final int TWO_CLASS_COUNT = 6; + private static final int TWO_CLASS_DEDUPLICATED_COUNT = 3; + @Parameters(name = "{0}") public static TestParametersCollection data() { return getTestParameters().withDexRuntimes().withAllApiLevels().build(); @@ -97,6 +108,14 @@ public void testR8TwoClasses() throws Exception { testForR8(parameters.getBackend()) .addProgramClasses(Foo.class, Bar.class) .setMinApi(parameters) + .addOptionsModification( + options -> + options + .getTestingOptions() + .setMixedSectionLayoutStrategyInspector( + codeLayoutInspector( + parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S), + true))) .addKeepAllClassesRule() .compile(); compile.run(parameters.getRuntime(), Foo.class).assertSuccessWithOutputLines(EXPECTED); @@ -110,6 +129,14 @@ public void testR8WithLinesTwoClasses() throws Exception { .addProgramClasses(Foo.class, Bar.class) .addKeepAttributeLineNumberTable() .setMinApi(parameters) + .addOptionsModification( + options -> + options + .getTestingOptions() + .setMixedSectionLayoutStrategyInspector( + codeLayoutInspector( + parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S), + true))) .addKeepAllClassesRule() .compile(); compile.run(parameters.getRuntime(), Foo.class).assertSuccessWithOutputLines(EXPECTED); @@ -122,6 +149,14 @@ public void testD8TwoClassesMappingOutput() throws Exception { testForD8(parameters.getBackend()) .addProgramClasses(Foo.class, Bar.class) .setMinApi(parameters) + .addOptionsModification( + options -> + options + .getTestingOptions() + .setMixedSectionLayoutStrategyInspector( + codeLayoutInspector( + parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S), + true))) .release() .internalEnableMappingOutput() .compile(); @@ -135,6 +170,11 @@ public void testD8TwoClassesNoMappingOutput() throws Exception { testForD8(parameters.getBackend()) .addProgramClasses(Foo.class, Bar.class) .setMinApi(parameters) + .addOptionsModification( + options -> + options + .getTestingOptions() + .setMixedSectionLayoutStrategyInspector(codeLayoutInspector(false, true))) .release() .compile(); compile.run(parameters.getRuntime(), Foo.class).assertSuccessWithOutputLines(EXPECTED); @@ -142,12 +182,46 @@ public void testD8TwoClassesNoMappingOutput() throws Exception { assertSizes(compile.writeToZip(), 6, 6); } + private MixedSectionLayoutInspector codeLayoutInspector( + boolean assumeDeduplicated, boolean twoClasses) { + return new MixedSectionLayoutInspector() { + @Override + public void inspectCodeLayout(int virtualFile, Collection layout) { + // We have as many methods, the ones sharing code are just last. + assertTrue(layout.size() == (twoClasses ? TWO_CLASS_COUNT : ONE_CLASS_COUNT)); + + ArrayList programMethods = new ArrayList<>(layout); + if (twoClasses) { + if (assumeDeduplicated) { + // The two init methods are shared. + // The two foo methods AND the bar method are shared, so they should be last. + // We sort by count, then fully qualified name. + assertTrue(programMethods.get(0).toString().contains("Foo.main")); + assertTrue(programMethods.get(1).toString().contains("Bar.")); + assertTrue(programMethods.get(2).toString().contains("Foo.")); + assertTrue(programMethods.get(3).toString().contains("Bar.foo")); + assertTrue(programMethods.get(4).toString().contains("Foo.bar")); + assertTrue(programMethods.get(5).toString().contains("Foo.foo")); + } else { + // We sort by name. + assertTrue(programMethods.get(0).toString().contains("Bar.")); + assertTrue(programMethods.get(1).toString().contains("Bar.foo")); + assertTrue(programMethods.get(2).toString().contains("Foo.")); + assertTrue(programMethods.get(3).toString().contains("Foo.bar")); + assertTrue(programMethods.get(4).toString().contains("Foo.foo")); + assertTrue(programMethods.get(5).toString().contains("Foo.main")); + } + } + } + }; + } + private void assertFooSizes(Path output) throws Exception { - assertSizes(output, 3, 4); + assertSizes(output, ONE_CLASS_DEDUPLICATED_COUNT, ONE_CLASS_COUNT); } private void assertFooAndBarSizes(Path output) throws Exception { - assertSizes(output, 3, 6); + assertSizes(output, TWO_CLASS_DEDUPLICATED_COUNT, TWO_CLASS_COUNT); } private void assertSizes(Path output, int deduppedSize, int originalSize) From 5d36716242d8728007bfccdb4965cc9641c2afa0 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Fri, 16 Jun 2023 10:31:12 +0200 Subject: [PATCH 127/153] Fix FileNotFoundException from art cache Change-Id: I986ab84c94aba710419d2a2b7811663639064d4a --- .../java/com/android/tools/r8/ToolHelper.java | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java index 55994a21df..069acbf3c5 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java @@ -829,19 +829,19 @@ public void run() { .put(DexVm.ART_6_0_1_HOST, "bin/art") .build(); - private static final List DALVIK_BOOT_LIBS = - ImmutableList.of( - "core-libart-hostdex.jar", - "core-hostdex.jar", - "apache-xml-hostdex.jar"); + private static final List DALVIK_4_0_BOOT_LIBS = + ImmutableList.of("core-hostdex.jar", "apache-xml-hostdex.jar"); - private static final List ART_BOOT_LIBS = - ImmutableList.of( - "core-libart-hostdex.jar", - "core-oj-hostdex.jar", - "apache-xml-hostdex.jar"); + private static final List DALVIK_4_4_BOOT_LIBS = + ImmutableList.of("core-libart-hostdex.jar", "core-hostdex.jar", "apache-xml-hostdex.jar"); + + private static final List ART_5_TO_6_BOOT_LIBS = + ImmutableList.of("core-libart-hostdex.jar"); - private static final List NEWER_ART_BOOT_LIBS = + private static final List ART_7_TO_10_BOOT_LIBS = + ImmutableList.of("core-libart-hostdex.jar", "core-oj-hostdex.jar", "apache-xml-hostdex.jar"); + + private static final List ART_12_PLUS_BOOT_LIBS = ImmutableList.of( "core-libart-hostdex.jar", "core-oj-hostdex.jar", @@ -853,18 +853,18 @@ public void run() { static { ImmutableMap.Builder> builder = ImmutableMap.builder(); builder - .put(DexVm.ART_DEFAULT, ART_BOOT_LIBS) - .put(DexVm.ART_14_0_0_HOST, NEWER_ART_BOOT_LIBS) - .put(DexVm.ART_13_0_0_HOST, NEWER_ART_BOOT_LIBS) - .put(DexVm.ART_12_0_0_HOST, NEWER_ART_BOOT_LIBS) - .put(DexVm.ART_10_0_0_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_9_0_0_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_8_1_0_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_7_0_0_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_6_0_1_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_5_1_1_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_4_4_4_HOST, DALVIK_BOOT_LIBS) - .put(DexVm.ART_4_0_4_HOST, DALVIK_BOOT_LIBS); + .put(DexVm.ART_DEFAULT, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_14_0_0_HOST, ART_12_PLUS_BOOT_LIBS) + .put(DexVm.ART_13_0_0_HOST, ART_12_PLUS_BOOT_LIBS) + .put(DexVm.ART_12_0_0_HOST, ART_12_PLUS_BOOT_LIBS) + .put(DexVm.ART_10_0_0_HOST, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_9_0_0_HOST, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_8_1_0_HOST, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_7_0_0_HOST, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_6_0_1_HOST, ART_5_TO_6_BOOT_LIBS) + .put(DexVm.ART_5_1_1_HOST, ART_5_TO_6_BOOT_LIBS) + .put(DexVm.ART_4_4_4_HOST, DALVIK_4_4_BOOT_LIBS) + .put(DexVm.ART_4_0_4_HOST, DALVIK_4_0_BOOT_LIBS); BOOT_LIBS = builder.build(); } @@ -888,7 +888,6 @@ public void run() { PRODUCT = builder.build(); } - private static Path getDexVmPath(DexVm vm) { DexVm.Version version = vm.getVersion(); Path base = Paths.get(TOOLS_DIR, "linux"); From 2ee4df2b7265e4ddea5e1c2ba1c0e95636c18dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Fri, 16 Jun 2023 12:35:26 +0200 Subject: [PATCH 128/153] Add redundant block check Change-Id: I23f0eb4fc71ef8e21e1024d55e292be7f8401849 --- .../com/android/tools/r8/ir/optimize/AssertionsRewriter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java index e1f8d9b1d1..e32a6f5f48 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java @@ -344,10 +344,11 @@ public void run( DexEncodedMethod method, IRCode code, DeadCodeRemover deadCodeRemover, Timing timing) { if (enabled) { timing.begin("Rewrite assertions"); - if (runInternal(method, code)) { + boolean needsDeadCodeRemoval = runInternal(method, code); + code.removeRedundantBlocks(); + if (needsDeadCodeRemoval) { deadCodeRemover.run(code, timing); } - code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); timing.end(); } From 2e146fbd2c2f1b7e04cf8be9d938bb06ee44f5cf Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Fri, 16 Jun 2023 14:25:32 +0200 Subject: [PATCH 129/153] Add input and output consumers for android resources This is in preperation of supporting resource shrinking in R8. Currently we do a simple pass through of the resources. Current API is marked as experimental in the api descriptions and compatability tests have not been added since we are not 100% sure on the final api yet Bug: 287399385 Change-Id: I8c45ebf0805a8ea1d3387f90106b9847dffba270 --- .../tools/r8/AndroidResourceConsumer.java | 12 ++ .../tools/r8/AndroidResourceInput.java | 35 +++++ .../tools/r8/AndroidResourceOutput.java | 20 +++ .../tools/r8/AndroidResourceProvider.java | 16 +++ .../ArchiveProtoAndroidResourceConsumer.java | 30 +++++ .../ArchiveProtoAndroidResourceProvider.java | 123 ++++++++++++++++++ src/main/java/com/android/tools/r8/R8.java | 46 +++++++ .../java/com/android/tools/r8/R8Command.java | 41 +++++- .../com/android/tools/r8/ResourcePath.java | 11 ++ .../tools/r8/utils/InternalOptions.java | 4 + .../AndroidResourceTestingUtils.java | 70 ++++++++++ .../AndroidResourcesPassthroughTest.java | 84 ++++++++++++ 12 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/AndroidResourceConsumer.java create mode 100644 src/main/java/com/android/tools/r8/AndroidResourceInput.java create mode 100644 src/main/java/com/android/tools/r8/AndroidResourceOutput.java create mode 100644 src/main/java/com/android/tools/r8/AndroidResourceProvider.java create mode 100644 src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java create mode 100644 src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java create mode 100644 src/main/java/com/android/tools/r8/ResourcePath.java create mode 100644 src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java create mode 100644 src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java diff --git a/src/main/java/com/android/tools/r8/AndroidResourceConsumer.java b/src/main/java/com/android/tools/r8/AndroidResourceConsumer.java new file mode 100644 index 0000000000..634611f04b --- /dev/null +++ b/src/main/java/com/android/tools/r8/AndroidResourceConsumer.java @@ -0,0 +1,12 @@ +// Copyright (c) 2023, 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; + +@Keep +public interface AndroidResourceConsumer { + + void accept(AndroidResourceOutput androidResource, DiagnosticsHandler diagnosticsHandler); + + default void finished(DiagnosticsHandler handler) {} +} diff --git a/src/main/java/com/android/tools/r8/AndroidResourceInput.java b/src/main/java/com/android/tools/r8/AndroidResourceInput.java new file mode 100644 index 0000000000..00f61c795d --- /dev/null +++ b/src/main/java/com/android/tools/r8/AndroidResourceInput.java @@ -0,0 +1,35 @@ +// Copyright (c) 2023, 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; + +import java.io.InputStream; + +/** + * Base interface for android resources (resource table, manifest, res folder files) + * + *

Android resources are provided to R8 to allow for combined code and resource shrinking. This + * allows us to reason about resources, and transitively other resources and code throughout the + * entire compilation pipeline. + */ +@Keep +public interface AndroidResourceInput extends Resource { + enum Kind { + // The AndroidManifest.xml file. + MANIFEST, + // The resource table, in either binary or proto format. + RESOURCE_TABLE, + // An xml file within the res folder. + XML_FILE, + // Any other binary file withing the res folder. + RES_FOLDER_FILE + } + + // The path, within the app, of the resource. + ResourcePath getPath(); + + Kind getKind(); + + InputStream getByteStream() throws ResourceException; +} diff --git a/src/main/java/com/android/tools/r8/AndroidResourceOutput.java b/src/main/java/com/android/tools/r8/AndroidResourceOutput.java new file mode 100644 index 0000000000..6b204b52ed --- /dev/null +++ b/src/main/java/com/android/tools/r8/AndroidResourceOutput.java @@ -0,0 +1,20 @@ +// Copyright (c) 2023, 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; + +/** + * Base interface for android resources (resource table, manifest, res folder files) + * + *

Android resources are provided to R8 to allow for combined code and resource shrinking. This + * allows us to reason about resources, and transitively other resources and code throughout the + * entire compilation pipeline. + */ +@Keep +public interface AndroidResourceOutput extends Resource { + // The path, within the app, of the resource. + ResourcePath getPath(); + + ByteDataView getByteDataView(); +} diff --git a/src/main/java/com/android/tools/r8/AndroidResourceProvider.java b/src/main/java/com/android/tools/r8/AndroidResourceProvider.java new file mode 100644 index 0000000000..cb44ff0891 --- /dev/null +++ b/src/main/java/com/android/tools/r8/AndroidResourceProvider.java @@ -0,0 +1,16 @@ +// Copyright (c) 2023, 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; + +import java.util.Collection; + +/** Android resource provider. */ +@Keep +public interface AndroidResourceProvider { + + // Provide all android resources + Collection getAndroidResources() throws ResourceException; + + default void finished(DiagnosticsHandler handler) {} +} diff --git a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java new file mode 100644 index 0000000000..c601e80596 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java @@ -0,0 +1,30 @@ +// Copyright (c) 2023, 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; + +import com.android.tools.r8.utils.ArchiveBuilder; +import com.android.tools.r8.utils.OutputBuilder; +import java.nio.file.Path; + +public class ArchiveProtoAndroidResourceConsumer implements AndroidResourceConsumer { + private final OutputBuilder outputBuilder; + + public ArchiveProtoAndroidResourceConsumer(Path outputPath) { + this.outputBuilder = new ArchiveBuilder(outputPath); + this.outputBuilder.open(); + } + + @Override + public void accept(AndroidResourceOutput androidResource, DiagnosticsHandler diagnosticsHandler) { + outputBuilder.addFile( + androidResource.getPath().location(), + androidResource.getByteDataView(), + diagnosticsHandler); + } + + @Override + public void finished(DiagnosticsHandler handler) { + outputBuilder.close(handler); + } +} diff --git a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java new file mode 100644 index 0000000000..ecf9970f8e --- /dev/null +++ b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java @@ -0,0 +1,123 @@ +// Copyright (c) 2023, 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; + +import com.android.tools.r8.AndroidResourceInput.Kind; +import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.origin.ArchiveEntryOrigin; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.FileUtils; +import com.google.common.io.ByteStreams; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Provides android resources from an archive. + * + *

The descriptor index is built eagerly upon creating the provider and subsequent requests for + * resources in the descriptor set will then force the read of zip entry contents. + */ +@Keep +public class ArchiveProtoAndroidResourceProvider implements AndroidResourceProvider { + private final Path archive; + private final Origin origin; + + private static final String MANIFEST_NAME = "AndroidManifest.xml"; + private static final String RESOURCE_TABLE = "resources.pb"; + private static final String RES_FOLDER = "res/"; + private static final String XML_SUFFIX = ".xml"; + + /** + * Creates an android resource provider from an archive. + * + * @param archive Zip archive to provide resources from. + * @param origin Origin of the archive. + */ + public ArchiveProtoAndroidResourceProvider(Path archive, Origin origin) { + this.archive = archive; + this.origin = origin; + } + + @Override + public Collection getAndroidResources() throws ResourceException { + try (ZipFile zipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8)) { + List resources = new ArrayList<>(); + final Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + ByteAndroidResourceInput resource = + new ByteAndroidResourceInput( + name, + getKindFromName(name), + ByteStreams.toByteArray(zipFile.getInputStream(entry)), + new ArchiveEntryOrigin(name, origin)); + resources.add(resource); + } + return resources; + } catch (IOException e) { + throw new ResourceException(origin, e); + } + } + + private Kind getKindFromName(String name) { + if (name.equals(MANIFEST_NAME)) { + return Kind.MANIFEST; + } + if (name.equals(RESOURCE_TABLE)) { + return Kind.RESOURCE_TABLE; + } + if (!name.startsWith(RES_FOLDER)) { + throw new CompilationError("Unexpected non resource entry " + name, origin); + } + if (name.endsWith(XML_SUFFIX)) { + return Kind.XML_FILE; + } + return Kind.RES_FOLDER_FILE; + } + + private static class ByteAndroidResourceInput implements AndroidResourceInput { + + private final String name; + private final Kind kind; + private final byte[] bytes; + private final Origin origin; + + public ByteAndroidResourceInput(String name, Kind kind, byte[] bytes, Origin origin) { + this.name = name; + this.kind = kind; + this.bytes = bytes; + this.origin = origin; + } + + @Override + public ResourcePath getPath() { + return () -> name; + } + + @Override + public Kind getKind() { + return kind; + } + + @Override + public InputStream getByteStream() throws ResourceException { + return new ByteArrayInputStream(bytes); + } + + @Override + public Origin getOrigin() { + return origin; + } + } +} diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 56d0a8c992..0ce7a4e9da 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -72,6 +72,7 @@ import com.android.tools.r8.optimize.proto.ProtoNormalizer; import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemover; import com.android.tools.r8.origin.CommandLineOrigin; +import com.android.tools.r8.origin.Origin; import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker; import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions; import com.android.tools.r8.repackaging.Repackaging; @@ -102,6 +103,7 @@ import com.android.tools.r8.synthesis.SyntheticFinalization; import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.ExceptionDiagnostic; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.SelfRetraceTest; @@ -804,6 +806,12 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO new DesugaredLibraryKeepRuleGenerator(appView).runIfNecessary(timing); + if (options.androidResourceProvider != null && options.androidResourceConsumer != null) { + // Currently this is simply a pass through of all resources. + writeAndroidResources( + options.androidResourceProvider, options.androidResourceConsumer, appView.reporter()); + } + // Generate the resulting application resources. writeApplication(appView, inputApp, executorService); @@ -822,6 +830,44 @@ private void run(AndroidApp inputApp, ExecutorService executorService) throws IO } } + private void writeAndroidResources( + AndroidResourceProvider androidResourceProvider, + AndroidResourceConsumer androidResourceConsumer, + DiagnosticsHandler diagnosticsHandler) { + try { + for (AndroidResourceInput androidResource : androidResourceProvider.getAndroidResources()) { + androidResourceConsumer.accept( + new AndroidResourceOutput() { + @Override + public ResourcePath getPath() { + return androidResource.getPath(); + } + + @Override + public ByteDataView getByteDataView() { + try { + return ByteDataView.of(ByteStreams.toByteArray(androidResource.getByteStream())); + } catch (IOException | ResourceException e) { + diagnosticsHandler.error(new ExceptionDiagnostic(e, androidResource.getOrigin())); + } + return null; + } + + @Override + public Origin getOrigin() { + return androidResource.getOrigin(); + } + }, + diagnosticsHandler); + } + } catch (ResourceException e) { + throw new RuntimeException("Cannot write android resources", e); + } finally { + androidResourceConsumer.finished(diagnosticsHandler); + androidResourceProvider.finished(diagnosticsHandler); + } + } + private static boolean allReferencesAssignedApiLevel( AppView appView) { if (!appView.options().apiModelingOptions().isCheckAllApiReferencesAreSet() diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index 7ec72c1b0a..4e8991388e 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java @@ -135,6 +135,8 @@ public void error(Diagnostic error) { private boolean enableMissingLibraryApiModeling = false; private boolean enableExperimentalKeepAnnotations = false; private SemanticVersion fakeCompilerVersion = null; + private AndroidResourceProvider androidResourceProvider = null; + private AndroidResourceConsumer androidResourceConsumer = null; private final ProguardConfigurationParserOptions.Builder parserOptionsBuilder = ProguardConfigurationParserOptions.builder().readEnvironment(); @@ -506,6 +508,28 @@ public Builder addStartupProfileProviders( return super.addStartupProfileProviders(startupProfileProviders); } + /** + * Exprimental API for supporting android resource shrinking. + * + *

Add an android resource provider, providing the resource table, manifest and res table + * entries. + */ + public Builder setAndroidResourceProvider(AndroidResourceProvider provider) { + this.androidResourceProvider = provider; + return this; + } + + /** + * Exprimental API for supporting android resource shrinking. + * + *

Add an android resource consumer, consuming the resource table, manifest and res table + * entries. + */ + public Builder setAndroidResourceConsumer(AndroidResourceConsumer consumer) { + this.androidResourceConsumer = consumer; + return this; + } + @Override void validate() { if (isPrintHelp()) { @@ -659,7 +683,9 @@ private R8Command makeR8Command() { getArtProfilesForRewriting(), getStartupProfileProviders(), getClassConflictResolver(), - getCancelCompilationChecker()); + getCancelCompilationChecker(), + androidResourceProvider, + androidResourceConsumer); if (inputDependencyGraphConsumer != null) { inputDependencyGraphConsumer.finished(); @@ -847,6 +873,8 @@ static String getUsageMessage() { private final FeatureSplitConfiguration featureSplitConfiguration; private final String synthesizedClassPrefix; private final boolean enableMissingLibraryApiModeling; + private final AndroidResourceProvider androidResourceProvider; + private final AndroidResourceConsumer androidResourceConsumer; /** Get a new {@link R8Command.Builder}. */ public static Builder builder() { @@ -941,7 +969,9 @@ private R8Command( List artProfilesForRewriting, List startupProfileProviders, ClassConflictResolver classConflictResolver, - CancelCompilationChecker cancelCompilationChecker) { + CancelCompilationChecker cancelCompilationChecker, + AndroidResourceProvider androidResourceProvider, + AndroidResourceConsumer androidResourceConsumer) { super( inputApp, mode, @@ -986,6 +1016,8 @@ private R8Command( this.featureSplitConfiguration = featureSplitConfiguration; this.synthesizedClassPrefix = synthesizedClassPrefix; this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling; + this.androidResourceProvider = androidResourceProvider; + this.androidResourceConsumer = androidResourceConsumer; } private R8Command(boolean printHelp, boolean printVersion) { @@ -1010,6 +1042,8 @@ private R8Command(boolean printHelp, boolean printVersion) { featureSplitConfiguration = null; synthesizedClassPrefix = null; enableMissingLibraryApiModeling = false; + androidResourceProvider = null; + androidResourceConsumer = null; } public DexItemFactory getDexItemFactory() { @@ -1182,6 +1216,9 @@ InternalOptions getInternalOptions() { internal.cancelCompilationChecker = getCancelCompilationChecker(); + internal.androidResourceProvider = androidResourceProvider; + internal.androidResourceConsumer = androidResourceConsumer; + if (!DETERMINISTIC_DEBUGGING) { assert internal.threadCount == ThreadUtils.NOT_SPECIFIED; internal.threadCount = getThreadCount(); diff --git a/src/main/java/com/android/tools/r8/ResourcePath.java b/src/main/java/com/android/tools/r8/ResourcePath.java new file mode 100644 index 0000000000..eab4d7ec64 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ResourcePath.java @@ -0,0 +1,11 @@ +// Copyright (c) 2023, 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; + +@Keep +public interface ResourcePath { + + // The location within the apk, bundle or resource directory, e.g., res/xml/foo.xml + String location(); +} 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 9afb2e9b39..09f5a8c3a0 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -6,6 +6,8 @@ import static com.android.tools.r8.utils.AndroidApiLevel.B; import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault; +import com.android.tools.r8.AndroidResourceConsumer; +import com.android.tools.r8.AndroidResourceProvider; import com.android.tools.r8.CancelCompilationChecker; import com.android.tools.r8.ClassFileConsumer; import com.android.tools.r8.CompilationMode; @@ -173,6 +175,8 @@ public DexItemFactory dexItemFactory() { // The state can only ever transition from false to true. private final AtomicBoolean cancelled = new AtomicBoolean(false); public CancelCompilationChecker cancelCompilationChecker = null; + public AndroidResourceProvider androidResourceProvider = null; + public AndroidResourceConsumer androidResourceConsumer = null; public boolean checkIfCancelled() { if (cancelCompilationChecker == null) { diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java new file mode 100644 index 0000000000..b58587b29b --- /dev/null +++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java @@ -0,0 +1,70 @@ +// Copyright (c) 2023, 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.androidresources; + +import java.nio.charset.StandardCharsets; + +public class AndroidResourceTestingUtils { + + // Taken from default empty android studio activity template + public static String TEST_MANIFEST = + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + " \n" + + " \n" + + "\n" + + " \n" + + " \n" + + " \n" + + "\n" + + ""; + + // TODO(287399385): Add testing utils for generating/consuming resource tables. + public static byte[] TEST_RESOURCE_TABLE = "RESOURCE_TABLE".getBytes(StandardCharsets.UTF_8); + + // The below byte arrays are lifted from the resource shrinkers DummyContent + + // A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY + public static final byte[] TINY_PNG = + new byte[] { + (byte) -119, (byte) 80, (byte) 78, (byte) 71, (byte) 13, (byte) 10, + (byte) 26, (byte) 10, (byte) 0, (byte) 0, (byte) 0, (byte) 13, + (byte) 73, (byte) 72, (byte) 68, (byte) 82, (byte) 0, (byte) 0, + (byte) 0, (byte) 1, (byte) 0, (byte) 0, (byte) 0, (byte) 1, + (byte) 8, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 58, + (byte) 126, (byte) -101, (byte) 85, (byte) 0, (byte) 0, (byte) 0, + (byte) 10, (byte) 73, (byte) 68, (byte) 65, (byte) 84, (byte) 120, + (byte) -38, (byte) 99, (byte) 96, (byte) 0, (byte) 0, (byte) 0, + (byte) 2, (byte) 0, (byte) 1, (byte) -27, (byte) 39, (byte) -34, + (byte) -4, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 73, + (byte) 69, (byte) 78, (byte) 68, (byte) -82, (byte) 66, (byte) 96, + (byte) -126 + }; + + // The XML document as a proto packed with AAPT2 + public static final byte[] TINY_PROTO_XML = + new byte[] {0xa, 0x3, 0x1a, 0x1, 0x78, 0x1a, 0x2, 0x8, 0x1}; +} diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java new file mode 100644 index 0000000000..f0bb82ad72 --- /dev/null +++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java @@ -0,0 +1,84 @@ +// Copyright (c) 2023, 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.androidresources; + +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.ArchiveProtoAndroidResourceConsumer; +import com.android.tools.r8.ArchiveProtoAndroidResourceProvider; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.origin.PathOrigin; +import com.android.tools.r8.utils.ZipUtils; +import com.android.tools.r8.utils.ZipUtils.ZipBuilder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class AndroidResourcesPassthroughTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + } + + @Test + public void testR8() throws Exception { + String manifestPath = "AndroidManifest.xml"; + String resourcePath = "resources.pb"; + String pngPath = "res/drawable/foo.png"; + String xmlPath = "res/xml/bar.xml"; + Path resources = + ZipBuilder.builder(temp.newFile("resources.zip").toPath()) + .addText(manifestPath, AndroidResourceTestingUtils.TEST_MANIFEST) + .addBytes(resourcePath, AndroidResourceTestingUtils.TEST_RESOURCE_TABLE) + .addBytes(pngPath, AndroidResourceTestingUtils.TINY_PNG) + .addBytes(xmlPath, AndroidResourceTestingUtils.TINY_PROTO_XML) + .build(); + Path output = temp.newFile("resources_out.zip").toPath(); + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters) + .addOptionsModification( + o -> { + o.androidResourceProvider = + new ArchiveProtoAndroidResourceProvider(resources, new PathOrigin(resources)); + o.androidResourceConsumer = new ArchiveProtoAndroidResourceConsumer(output); + }) + .addKeepMainRule(FooBar.class) + .run(parameters.getRuntime(), FooBar.class) + .assertSuccessWithOutputLines("Hello World"); + assertTrue( + Arrays.equals( + ZipUtils.readSingleEntry(output, manifestPath), + AndroidResourceTestingUtils.TEST_MANIFEST.getBytes(StandardCharsets.UTF_8))); + assertTrue( + Arrays.equals( + ZipUtils.readSingleEntry(output, resourcePath), + AndroidResourceTestingUtils.TEST_RESOURCE_TABLE)); + assertTrue( + Arrays.equals( + ZipUtils.readSingleEntry(output, pngPath), AndroidResourceTestingUtils.TINY_PNG)); + assertTrue( + Arrays.equals( + ZipUtils.readSingleEntry(output, xmlPath), AndroidResourceTestingUtils.TINY_PROTO_XML)); + } + + public static class FooBar { + + public static void main(String[] args) { + System.out.println("Hello World"); + } + } +} From 5e8d3427d1107663cd65d8dc05fe131c7bdaea9a Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Mon, 19 Jun 2023 10:15:44 +0200 Subject: [PATCH 130/153] Remove redundant blocks. Change-Id: Ifbc69798e57e2225bfb2820a9351bcaf9f70cfde --- .../java/com/android/tools/r8/ir/conversion/IRConverter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index e9431230fa..894117603a 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -1048,6 +1048,7 @@ public void removeDeadCodeAndFinalizeIR( if (stringSwitchRemover != null) { stringSwitchRemover.run(code); } + code.removeRedundantBlocks(); deadCodeRemover.run(code, timing); finalizeIR( code, From 7283ec28898a8002f849c5237582dd5e048ee463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Mon, 19 Jun 2023 10:22:05 +0200 Subject: [PATCH 131/153] Remove more redundant blocks Change-Id: I5d758440e910d0b64cf6504d67405b808f1a064e --- .../ir/optimize/ClassInitializerDefaultsOptimization.java | 3 +++ .../r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java index 8d2b4bb989..9c94862e77 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java @@ -301,6 +301,9 @@ public ClassInitializerDefaultsResult optimize(IRCode code, OptimizationFeedback fieldsWithStaticValues.forEach(DexEncodedField::setStaticValue); } + if (!fieldsWithStaticValues.isEmpty()) { + code.removeRedundantBlocks(); + } return new ClassInitializerDefaultsResult(fieldsWithStaticValues); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java index 855f98550d..02e3b25176 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java @@ -78,7 +78,7 @@ public static void workaroundSwitchMaxIntBug(IRCode code, AppView appView) { } private static void rewriteSwitchForMaxIntOnly(IRCode code, AppView appView) { - boolean needToSplitCriticalEdges = false; + boolean hasChanged = false; BranchSimplifier branchSimplifier = new BranchSimplifier(appView); ListIterator blocksIterator = code.listIterator(); while (blocksIterator.hasNext()) { @@ -108,7 +108,7 @@ private static void rewriteSwitchForMaxIntOnly(IRCode code, AppView appView) ImmutableList.of(newSwitchSequences), outliers); } - needToSplitCriticalEdges = true; + hasChanged = true; } } } @@ -117,8 +117,9 @@ private static void rewriteSwitchForMaxIntOnly(IRCode code, AppView appView) // Rewriting of switches introduces new branching structure. It relies on critical edges // being split on the way in but does not maintain this property. We therefore split // critical edges at exit. - if (needToSplitCriticalEdges) { + if (hasChanged) { code.splitCriticalEdges(); + code.removeRedundantBlocks(); } } From feab0c3389ac425d2ed6d3aa426445fb4edaac2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Fri, 16 Jun 2023 10:33:15 +0200 Subject: [PATCH 132/153] Make ThrowCatchOptimizer a CodeRewriterPass Bug: b/284304606 Change-Id: I231eb42accbb2310b3eec0bd6c096d0b54117b74 --- .../tools/r8/ir/conversion/IRConverter.java | 11 +---- .../passes/ThrowCatchOptimizer.java | 42 +++++++++++++++---- .../shaking/EnqueuerDeferredTracingImpl.java | 2 +- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 894117603a..327c74c8b1 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -760,9 +760,7 @@ Timing optimize( new StringBuilderAppendOptimizer(appView).run(code, timing); } new SparseConditionalConstantPropagation(appView, code).run(code, timing); - timing.begin("Rewrite always throwing instructions"); - new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(code); - timing.end(); + new ThrowCatchOptimizer(appView, isDebugMode).run(code, timing); timing.begin("Simplify control flow"); if (new BranchSimplifier(appView).simplifyBranches(code)) { new TrivialCheckCastAndInstanceOfRemover(appView) @@ -788,13 +786,6 @@ Timing optimize( invertConditionalsForTesting(code); } - if (!isDebugMode) { - timing.begin("Rewrite throw NPE"); - new ThrowCatchOptimizer(appView).rewriteThrowNullPointerException(code); - timing.end(); - previous = printMethod(code, "IR after rewrite throw null (SSA)", previous); - } - timing.begin("Optimize class initializers"); ClassInitializerDefaultsResult classInitializerDefaultsResult = classInitializerDefaultsOptimization.optimize(code, feedback); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java index f34a6bd20e..f5a684130b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java @@ -5,10 +5,10 @@ package com.android.tools.r8.ir.conversion.passes; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; @@ -32,6 +32,7 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -40,18 +41,41 @@ import java.util.ListIterator; import java.util.Set; -public class ThrowCatchOptimizer { +public class ThrowCatchOptimizer extends CodeRewriterPass { - private final AppView appView; - private final DexItemFactory dexItemFactory; + private final boolean rewriteThrowNull; + + public ThrowCatchOptimizer(AppView appView, boolean isDebug) { + super(appView); + this.rewriteThrowNull = !isDebug; + } public ThrowCatchOptimizer(AppView appView) { - this.appView = appView; - this.dexItemFactory = appView.dexItemFactory(); + super(appView); + this.rewriteThrowNull = false; + } + + @Override + protected String getTimingId() { + return "ThrowCatchOptimizer"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return true; + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + optimizeAlwaysThrowingInstructions(code); + if (rewriteThrowNull) { + rewriteThrowNullPointerException(code); + } + return CodeRewriterResult.NONE; } // Rewrite 'throw new NullPointerException()' to 'throw null'. - public void rewriteThrowNullPointerException(IRCode code) { + private void rewriteThrowNullPointerException(IRCode code) { boolean shouldRemoveUnreachableBlocks = false; for (BasicBlock block : code.blocks) { InstructionListIterator it = block.listIterator(code); @@ -64,7 +88,7 @@ public void rewriteThrowNullPointerException(IRCode code) { if (appView .dexItemFactory() .objectsMethods - .isRequireNonNullMethod(code.method().getReference())) { + .isRequireNonNullMethod(code.context().getReference())) { continue; } @@ -192,7 +216,7 @@ public void rewriteThrowNullPointerException(IRCode code) { // Find all instructions that always throw, split the block after each such instruction and follow // it with a block throwing a null value (which should result in NPE). Note that this throw is not // expected to be ever reached, but is intended to satisfy verifier. - public void optimizeAlwaysThrowingInstructions(IRCode code) { + private void optimizeAlwaysThrowingInstructions(IRCode code) { Set affectedValues = Sets.newIdentityHashSet(); Set blocksToRemove = Sets.newIdentityHashSet(); ListIterator blockIterator = code.listIterator(); diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java index 1c0d3de9d7..f7cf0b5a21 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java @@ -270,7 +270,7 @@ private void rewriteMethod( rewriter.rewriteCode(ir, initializedClassesWithContexts, prunedFields); // Run dead code elimination. - new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(ir); + new ThrowCatchOptimizer(appView).run(ir, Timing.empty()); rewriter.getDeadCodeRemover().run(ir, Timing.empty()); // Finalize to class files or dex. From 3a0b2307ac573ffe3917da031aa3948084aded27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Fri, 16 Jun 2023 10:42:17 +0200 Subject: [PATCH 133/153] BranchSimplifier as CodeRewriterPass Bug: b/284304606 Change-Id: I8c6d012d709b68568f7534816960c4a22150910e --- .../tools/r8/ir/conversion/IRConverter.java | 8 +- .../conversion/passes/BranchSimplifier.java | 107 ++++++++++++++---- .../passes/SwitchCaseEliminator.java | 6 +- .../passes/result/CodeRewriterResult.java | 5 + .../r8/ir/optimize/ConstantCanonicalizer.java | 3 +- .../tools/r8/ir/optimize/DeadCodeRemover.java | 6 +- .../optimize/classinliner/ClassInliner.java | 2 +- 7 files changed, 101 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 327c74c8b1..1fa60a3fff 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -761,12 +761,13 @@ Timing optimize( } new SparseConditionalConstantPropagation(appView, code).run(code, timing); new ThrowCatchOptimizer(appView, isDebugMode).run(code, timing); - timing.begin("Simplify control flow"); - if (new BranchSimplifier(appView).simplifyBranches(code)) { + if (new BranchSimplifier(appView) + .run(code, timing) + .asControlFlowSimplificationResult() + .anyAffectedValues()) { new TrivialCheckCastAndInstanceOfRemover(appView) .run(code, methodProcessor, methodProcessingContext, timing); } - timing.end(); splitBranch.run(code, timing); if (options.enableRedundantConstNumberOptimization) { timing.begin("Remove const numbers"); @@ -925,7 +926,6 @@ Timing optimize( // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL. assert code.verifyNoNullabilityBottomTypes(); - assert code.verifyTypes(appView); previous = diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index 28ddb0119a..b39431b805 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -4,6 +4,10 @@ package com.android.tools.r8.ir.conversion.passes; +import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.NO_CHANGE; +import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.create; + +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; @@ -35,10 +39,10 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.code.Xor; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOutputMode; import com.android.tools.r8.utils.LongInterval; import com.google.common.collect.ImmutableList; @@ -59,23 +63,36 @@ import java.util.PriorityQueue; import java.util.Set; -public class BranchSimplifier { - - private final AppView appView; - private final InternalOptions options; +public class BranchSimplifier extends CodeRewriterPass { public BranchSimplifier(AppView appView) { - this.appView = appView; - this.options = appView.options(); + super(appView); + } + + @Override + protected String getTimingId() { + return "BranchSimplifier"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return true; + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + ControlFlowSimplificationResult switchResult = rewriteSwitch(code); + ControlFlowSimplificationResult ifResult = simplifyIf(code); + return switchResult.combine(ifResult); } - public boolean simplifyBranches(IRCode code) { - boolean anyAffectedValues = rewriteSwitch(code); - anyAffectedValues |= simplifyIf(code).anyAffectedValues(); - return anyAffectedValues; + public ControlFlowSimplificationResult simplifyBranches(IRCode code) { + ControlFlowSimplificationResult switchResult = rewriteSwitch(code); + ControlFlowSimplificationResult ifResult = simplifyIf(code); + return switchResult.combine(ifResult); } - public ControlFlowSimplificationResult simplifyIf(IRCode code) { + private ControlFlowSimplificationResult simplifyIf(IRCode code) { BasicBlockBehavioralSubsumption behavioralSubsumption = new BasicBlockBehavioralSubsumption(appView, code); boolean simplified = false; @@ -126,10 +143,26 @@ public ControlFlowSimplificationResult simplifyIf(IRCode code) { } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified); + return create(!affectedValues.isEmpty(), simplified); } - public static class ControlFlowSimplificationResult { + public static class ControlFlowSimplificationResult implements CodeRewriterResult { + + static ControlFlowSimplificationResult create( + boolean anyAffectedValues, boolean anySimplifications) { + if (anyAffectedValues) { + assert anySimplifications; + return ALL_CHANGED; + } + return anySimplifications ? ONLY_SIMPLIFICATIONS : NO_CHANGE; + } + + static final ControlFlowSimplificationResult ALL_CHANGED = + new ControlFlowSimplificationResult(true, true); + static final ControlFlowSimplificationResult ONLY_SIMPLIFICATIONS = + new ControlFlowSimplificationResult(false, true); + static final ControlFlowSimplificationResult NO_CHANGE = + new ControlFlowSimplificationResult(false, false); private final boolean anyAffectedValues; private final boolean anySimplifications; @@ -140,6 +173,10 @@ private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySi this.anySimplifications = anySimplifications; } + public ControlFlowSimplificationResult asControlFlowSimplificationResult() { + return this; + } + public boolean anyAffectedValues() { return anyAffectedValues; } @@ -147,6 +184,18 @@ public boolean anyAffectedValues() { public boolean anySimplifications() { return anySimplifications; } + + @Override + public boolean hasChanged() { + assert !anyAffectedValues || anySimplifications; + return anySimplifications(); + } + + public ControlFlowSimplificationResult combine(ControlFlowSimplificationResult ifResult) { + return create( + anyAffectedValues || ifResult.anyAffectedValues, + anySimplifications || ifResult.anySimplifications); + } } private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) { @@ -567,22 +616,25 @@ private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) { return true; } - private boolean rewriteSwitch(IRCode code) { + private ControlFlowSimplificationResult rewriteSwitch(IRCode code) { return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance()); } - private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + private ControlFlowSimplificationResult rewriteSwitch( + IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { if (!options.isSwitchRewritingEnabled()) { - return false; + return NO_CHANGE; } if (!code.metadata().mayHaveSwitch()) { - return false; + return NO_CHANGE; } return rewriteSwitchFull(code, switchCaseAnalyzer); } - private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + private ControlFlowSimplificationResult rewriteSwitchFull( + IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { boolean needToRemoveUnreachableBlocks = false; + boolean anySimplifications = false; ListIterator blocksIterator = code.listIterator(); while (blocksIterator.hasNext()) { BasicBlock block = blocksIterator.next(); @@ -594,6 +646,7 @@ private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnal if (options.testing.enableDeadSwitchCaseElimination) { SwitchCaseEliminator eliminator = removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer); + anySimplifications |= eliminator.canBeOptimized(); if (eliminator.mayHaveIntroducedUnreachableBlocks()) { needToRemoveUnreachableBlocks = true; } @@ -608,7 +661,8 @@ private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnal theSwitch = instruction.asSwitch(); } if (theSwitch.isIntSwitch()) { - rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); + anySimplifications |= + rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); } } } @@ -621,12 +675,13 @@ private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnal Set affectedValues = needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of(); - if (!affectedValues.isEmpty()) { + boolean anyAffectedValues = !affectedValues.isEmpty(); + if (anyAffectedValues) { new TypeAnalysis(appView).narrowing(affectedValues); } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - return !affectedValues.isEmpty(); + return create(anyAffectedValues, anySimplifications); } public void rewriteSingleKeySwitchToIf( @@ -652,19 +707,19 @@ public void rewriteSingleKeySwitchToIf( iterator.replaceCurrentInstruction(replacement); } - private void rewriteIntSwitch( + private boolean rewriteIntSwitch( IRCode code, ListIterator blockIterator, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) { if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) { - return; + return false; } if (theSwitch.numberOfKeys() == 1) { rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch); - return; + return true; } // If there are more than 1 key, we use the following algorithm to find keys to combine. @@ -753,7 +808,9 @@ private void rewriteIntSwitch( if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) { convertSwitchToSwitchAndIfs( code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers); + return true; } + return false; } // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java index cbda6309d0..4a1a1d4e7b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java @@ -45,7 +45,7 @@ private boolean allSwitchCasesMarkedForRemoval() { && switchCasesToBeRemoved.size() == theSwitch.numberOfKeys(); } - private boolean canBeOptimized() { + boolean canBeOptimized() { assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty(); return switchCasesToBeRemoved != null || hasAlwaysHitCase() || !isFallthroughLive(); } @@ -96,7 +96,7 @@ void markSwitchFallthroughAsNeverHit() { liveFallthrough = false; } - boolean optimize() { + void optimize() { if (canBeOptimized()) { int originalNumberOfSuccessors = block.getSuccessors().size(); IntList removedSuccessorIndices = unlinkDeadSuccessors(); @@ -107,9 +107,7 @@ boolean optimize() { // Replace switch by a new switch where the dead switch cases have been removed. replaceSwitchByOptimizedSwitch(originalNumberOfSuccessors, removedSuccessorIndices); } - return true; } - return false; } private IntList unlinkDeadSuccessors() { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java index 68d5c0d2ff..1af288a003 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java @@ -5,6 +5,7 @@ package com.android.tools.r8.ir.conversion.passes.result; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult; public interface CodeRewriterResult { @@ -37,4 +38,8 @@ public boolean hasChanged() { } boolean hasChanged(); + + default ControlFlowSimplificationResult asControlFlowSimplificationResult() { + throw new Unreachable("Not a control flow simplification result."); + } } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java index c7ddc680e5..13f049be27 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java @@ -43,6 +43,7 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.utils.OptionalBool; +import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.Hash.Strategy; @@ -288,7 +289,7 @@ public boolean equals(Instruction a, Instruction b) { shouldSimplifyIfs |= code.removeAllDeadAndTrivialPhis(); if (shouldSimplifyIfs) { - branchSimplifier.simplifyIf(code); + branchSimplifier.run(code, Timing.empty()); } code.removeRedundantBlocks(); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index e9b180c578..c6cbe68e56 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java @@ -60,11 +60,15 @@ public void run(IRCode code, Timing timing) { removeDeadInstructions(worklist, code, block, valueIsDeadAnalysis); removeDeadPhis(worklist, block, valueIsDeadAnalysis); } - } while (branchSimplifier.simplifyIf(code).anySimplifications() + } while (branchSimplifier + .simplifyBranches(code) + .asControlFlowSimplificationResult() + .anySimplifications() || removeUnneededCatchHandlers(code)); code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + assert verifyNoDeadCode(code); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index d3d2a08bff..b46d0a5bfa 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -252,7 +252,7 @@ public final void processMethodCode( new TrivialCheckCastAndInstanceOfRemover(appView) .run(code, methodProcessor, methodProcessingContext, Timing.empty()); // If a method was inlined we may be able to prune additional branches. - new BranchSimplifier(appView).simplifyBranches(code); + new BranchSimplifier(appView).run(code, Timing.empty()); // If a method was inlined we may see more trivial computation/conversion of String. boolean isDebugMode = appView.options().debug || method.getOrComputeReachabilitySensitive(appView); From 230da2811d55012970f0bdfe334f08f816b1b314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Fri, 16 Jun 2023 11:17:55 +0200 Subject: [PATCH 134/153] Split RedundantConstNumberRemover from CodeRewriter Bug: b/284304606 Change-Id: I181fa6ebcd11a2c2c8a8857498db1e0440541e52 --- .../tools/r8/ir/conversion/IRConverter.java | 7 +- .../passes/RedundantConstNumberRemover.java | 278 ++++++++++++++++++ .../tools/r8/ir/optimize/CodeRewriter.java | 243 --------------- 3 files changed, 280 insertions(+), 248 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 1fa60a3fff..994d45bea8 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -36,6 +36,7 @@ import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.ir.conversion.passes.NaturalIntLoopRemover; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; +import com.android.tools.r8.ir.conversion.passes.RedundantConstNumberRemover; import com.android.tools.r8.ir.conversion.passes.SplitBranch; import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer; import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; @@ -769,11 +770,7 @@ Timing optimize( .run(code, methodProcessor, methodProcessingContext, timing); } splitBranch.run(code, timing); - if (options.enableRedundantConstNumberOptimization) { - timing.begin("Remove const numbers"); - codeRewriter.redundantConstNumberRemoval(code); - timing.end(); - } + new RedundantConstNumberRemover(appView).run(code, timing); if (RedundantFieldLoadAndStoreElimination.shouldRun(appView, code)) { timing.begin("Remove field loads"); new RedundantFieldLoadAndStoreElimination(appView, code).run(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java new file mode 100644 index 0000000000..70402ccd47 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java @@ -0,0 +1,278 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.ConstNumber; +import com.android.tools.r8.ir.code.DominatorTree; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.If; +import com.android.tools.r8.ir.code.IfType; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; +import com.android.tools.r8.utils.LazyBox; +import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +/** + * This optimization exploits that we can sometimes learn the constant value of an SSA value that + * flows into an if-eq of if-neq instruction. + * + *

Consider the following example: + * + *

+ * 1. if (obj != null) {
+ * 2.  return doStuff();
+ * 3. }
+ * 4. return null;
+ * 
+ * + *

Since we know that `obj` is null in all blocks that are dominated by the false-target of the + * if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and thereby + * save a const-number instruction. + */ +public class RedundantConstNumberRemover extends CodeRewriterPass { + + public RedundantConstNumberRemover(AppView appView) { + super(appView); + } + + @Override + protected String getTimingId() { + return "RedundantConstNumberRemover"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return options.enableRedundantConstNumberOptimization && code.metadata().mayHaveConstNumber(); + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + redundantConstNumberRemoval(code); + return CodeRewriterResult.NONE; + } + + public void redundantConstNumberRemoval(IRCode code) { + if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() + && !appView.options().testing.forceRedundantConstNumberRemoval) { + // See also b/124152497. + return; + } + + LazyBox>> constantsByValue = + new LazyBox<>(() -> getConstantsByValue(code)); + LazyBox dominatorTree = new LazyBox<>(() -> new DominatorTree(code)); + + boolean changed = false; + for (BasicBlock block : code.blocks) { + Instruction lastInstruction = block.getInstructions().getLast(); + if (!lastInstruction.isIf()) { + continue; + } + + If ifInstruction = lastInstruction.asIf(); + IfType type = ifInstruction.getType(); + + Value lhs = ifInstruction.inValues().get(0); + Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null; + + if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) { + // We can only conclude anything from an if-instruction if it is a zero-test or if one of + // the two operands is a constant. + continue; + } + + // If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values + // of the if-instruction from the outcome of the if-instruction. + if (type != IfType.EQ && type != IfType.NE) { + continue; + } + + BasicBlock trueTarget, falseTarget; + if (type == IfType.EQ) { + trueTarget = ifInstruction.getTrueTarget(); + falseTarget = ifInstruction.fallthroughBlock(); + } else { + falseTarget = ifInstruction.getTrueTarget(); + trueTarget = ifInstruction.fallthroughBlock(); + } + + if (ifInstruction.isZeroTest()) { + changed |= + replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree); + if (lhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + 1, lhs, falseTarget, constantsByValue, code, dominatorTree); + } + } else { + assert rhs != null; + if (lhs.isConstNumber()) { + ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber(); + changed |= + replaceDominatedConstNumbers( + lhsAsNumber.getRawValue(), + rhs, + trueTarget, + constantsByValue, + code, + dominatorTree); + if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + negateBoolean(lhsAsNumber), + rhs, + falseTarget, + constantsByValue, + code, + dominatorTree); + } + } else { + assert rhs.isConstNumber(); + ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber(); + changed |= + replaceDominatedConstNumbers( + rhsAsNumber.getRawValue(), + lhs, + trueTarget, + constantsByValue, + code, + dominatorTree); + if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + negateBoolean(rhsAsNumber), + lhs, + falseTarget, + constantsByValue, + code, + dominatorTree); + } + } + } + + if (constantsByValue.computeIfAbsent().isEmpty()) { + break; + } + } + + if (changed) { + code.removeAllDeadAndTrivialPhis(); + } + assert code.isConsistentSSA(appView); + } + + private static Long2ReferenceMap> getConstantsByValue(IRCode code) { + // A map from the raw value of constants in `code` to the const number instructions that define + // the given raw value (irrespective of the type of the raw value). + Long2ReferenceMap> constantsByValue = new Long2ReferenceOpenHashMap<>(); + + // Initialize `constantsByValue`. + for (Instruction instruction : code.instructions()) { + if (instruction.isConstNumber()) { + ConstNumber constNumber = instruction.asConstNumber(); + if (constNumber.outValue().hasLocalInfo()) { + // Not necessarily constant, because it could be changed in the debugger. + continue; + } + long rawValue = constNumber.getRawValue(); + if (constantsByValue.containsKey(rawValue)) { + constantsByValue.get(rawValue).add(constNumber); + } else { + List list = new ArrayList<>(); + list.add(constNumber); + constantsByValue.put(rawValue, list); + } + } + } + return constantsByValue; + } + + private static int negateBoolean(ConstNumber number) { + assert number.outValue().knownToBeBoolean(); + return number.getRawValue() == 0 ? 1 : 0; + } + + private boolean replaceDominatedConstNumbers( + long withValue, + Value newValue, + BasicBlock dominator, + LazyBox>> constantsByValueSupplier, + IRCode code, + LazyBox dominatorTree) { + if (newValue.hasLocalInfo()) { + // We cannot replace a constant with a value that has local info, because that could change + // debugging behavior. + return false; + } + + Long2ReferenceMap> constantsByValue = + constantsByValueSupplier.computeIfAbsent(); + List constantsWithValue = constantsByValue.get(withValue); + if (constantsWithValue == null || constantsWithValue.isEmpty()) { + return false; + } + + boolean changed = false; + + ListIterator constantWithValueIterator = constantsWithValue.listIterator(); + while (constantWithValueIterator.hasNext()) { + ConstNumber constNumber = constantWithValueIterator.next(); + Value value = constNumber.outValue(); + assert !value.hasLocalInfo(); + assert constNumber.getRawValue() == withValue; + + BasicBlock block = constNumber.getBlock(); + + // If the following condition does not hold, then the if-instruction does not dominate the + // block containing the constant, although the true or false target does. + if (block == dominator && block.getPredecessors().size() != 1) { + // This should generally not happen, but it is possible to write bytecode where it does. + assert false; + continue; + } + + if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) { + // We cannot replace a boolean by a none-boolean since that can lead to verification + // errors. For example, the following code fails with "register v1 has type Imprecise + // Constant: 127 but expected Boolean return-1nr". + // + // public boolean convertIntToBoolean(int v1) { + // const/4 v0, 0x1 + // if-eq v1, v0, :eq_true + // const/4 v1, 0x0 + // :eq_true + // return v1 + // } + continue; + } + + if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) { + if (newValue.getType().lessThanOrEqual(value.getType(), appView)) { + value.replaceUsers(newValue); + block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead(); + constantWithValueIterator.remove(); + changed = true; + } else if (value.getType().isNullType()) { + // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of + // the use sites of `value` without introducing a type error. + } + } + } + + if (constantsWithValue.isEmpty()) { + constantsByValue.remove(withValue); + } + + return changed; + } +} 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 e6968798e7..19f053fa46 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 @@ -21,11 +21,9 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; import com.android.tools.r8.ir.code.DebugLocalWrite; import com.android.tools.r8.ir.code.DebugLocalsChange; -import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; @@ -40,7 +38,6 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; -import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.common.collect.Streams; @@ -51,11 +48,7 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; -import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; -import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -import java.util.ArrayList; import java.util.List; -import java.util.ListIterator; import java.util.Set; public class CodeRewriter { @@ -209,242 +202,6 @@ public void simplifyDebugLocals(IRCode code) { } } - /** - * This optimization exploits that we can sometimes learn the constant value of an SSA value that - * flows into an if-eq of if-neq instruction. - * - *

Consider the following example: - * - *

-   * 1. if (obj != null) {
-   * 2.  return doStuff();
-   * 3. }
-   * 4. return null;
-   * 
- * - *

Since we know that `obj` is null in all blocks that are dominated by the false-target of the - * if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and - * thereby save a const-number instruction. - */ - public void redundantConstNumberRemoval(IRCode code) { - if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() - && !appView.options().testing.forceRedundantConstNumberRemoval) { - // See also b/124152497. - return; - } - - if (!code.metadata().mayHaveConstNumber()) { - return; - } - - LazyBox>> constantsByValue = - new LazyBox<>(() -> getConstantsByValue(code)); - LazyBox dominatorTree = new LazyBox<>(() -> new DominatorTree(code)); - - boolean changed = false; - for (BasicBlock block : code.blocks) { - Instruction lastInstruction = block.getInstructions().getLast(); - if (!lastInstruction.isIf()) { - continue; - } - - If ifInstruction = lastInstruction.asIf(); - IfType type = ifInstruction.getType(); - - Value lhs = ifInstruction.inValues().get(0); - Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null; - - if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) { - // We can only conclude anything from an if-instruction if it is a zero-test or if one of - // the two operands is a constant. - continue; - } - - // If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values - // of the if-instruction from the outcome of the if-instruction. - if (type != IfType.EQ && type != IfType.NE) { - continue; - } - - BasicBlock trueTarget, falseTarget; - if (type == IfType.EQ) { - trueTarget = ifInstruction.getTrueTarget(); - falseTarget = ifInstruction.fallthroughBlock(); - } else { - falseTarget = ifInstruction.getTrueTarget(); - trueTarget = ifInstruction.fallthroughBlock(); - } - - if (ifInstruction.isZeroTest()) { - changed |= - replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree); - if (lhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - 1, lhs, falseTarget, constantsByValue, code, dominatorTree); - } - } else { - assert rhs != null; - if (lhs.isConstNumber()) { - ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber(); - changed |= - replaceDominatedConstNumbers( - lhsAsNumber.getRawValue(), - rhs, - trueTarget, - constantsByValue, - code, - dominatorTree); - if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - negateBoolean(lhsAsNumber), - rhs, - falseTarget, - constantsByValue, - code, - dominatorTree); - } - } else { - assert rhs.isConstNumber(); - ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber(); - changed |= - replaceDominatedConstNumbers( - rhsAsNumber.getRawValue(), - lhs, - trueTarget, - constantsByValue, - code, - dominatorTree); - if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - negateBoolean(rhsAsNumber), - lhs, - falseTarget, - constantsByValue, - code, - dominatorTree); - } - } - } - - if (constantsByValue.computeIfAbsent().isEmpty()) { - break; - } - } - - if (changed) { - code.removeAllDeadAndTrivialPhis(); - } - assert code.isConsistentSSA(appView); - } - - private static Long2ReferenceMap> getConstantsByValue(IRCode code) { - // A map from the raw value of constants in `code` to the const number instructions that define - // the given raw value (irrespective of the type of the raw value). - Long2ReferenceMap> constantsByValue = new Long2ReferenceOpenHashMap<>(); - - // Initialize `constantsByValue`. - for (Instruction instruction : code.instructions()) { - if (instruction.isConstNumber()) { - ConstNumber constNumber = instruction.asConstNumber(); - if (constNumber.outValue().hasLocalInfo()) { - // Not necessarily constant, because it could be changed in the debugger. - continue; - } - long rawValue = constNumber.getRawValue(); - if (constantsByValue.containsKey(rawValue)) { - constantsByValue.get(rawValue).add(constNumber); - } else { - List list = new ArrayList<>(); - list.add(constNumber); - constantsByValue.put(rawValue, list); - } - } - } - return constantsByValue; - } - - private static int negateBoolean(ConstNumber number) { - assert number.outValue().knownToBeBoolean(); - return number.getRawValue() == 0 ? 1 : 0; - } - - private boolean replaceDominatedConstNumbers( - long withValue, - Value newValue, - BasicBlock dominator, - LazyBox>> constantsByValueSupplier, - IRCode code, - LazyBox dominatorTree) { - if (newValue.hasLocalInfo()) { - // We cannot replace a constant with a value that has local info, because that could change - // debugging behavior. - return false; - } - - Long2ReferenceMap> constantsByValue = - constantsByValueSupplier.computeIfAbsent(); - List constantsWithValue = constantsByValue.get(withValue); - if (constantsWithValue == null || constantsWithValue.isEmpty()) { - return false; - } - - boolean changed = false; - - ListIterator constantWithValueIterator = constantsWithValue.listIterator(); - while (constantWithValueIterator.hasNext()) { - ConstNumber constNumber = constantWithValueIterator.next(); - Value value = constNumber.outValue(); - assert !value.hasLocalInfo(); - assert constNumber.getRawValue() == withValue; - - BasicBlock block = constNumber.getBlock(); - - // If the following condition does not hold, then the if-instruction does not dominate the - // block containing the constant, although the true or false target does. - if (block == dominator && block.getPredecessors().size() != 1) { - // This should generally not happen, but it is possible to write bytecode where it does. - assert false; - continue; - } - - if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) { - // We cannot replace a boolean by a none-boolean since that can lead to verification - // errors. For example, the following code fails with "register v1 has type Imprecise - // Constant: 127 but expected Boolean return-1nr". - // - // public boolean convertIntToBoolean(int v1) { - // const/4 v0, 0x1 - // if-eq v1, v0, :eq_true - // const/4 v1, 0x0 - // :eq_true - // return v1 - // } - continue; - } - - if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) { - if (newValue.getType().lessThanOrEqual(value.getType(), appView)) { - value.replaceUsers(newValue); - block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead(); - constantWithValueIterator.remove(); - changed = true; - } else if (value.getType().isNullType()) { - // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of - // the use sites of `value` without introducing a type error. - } - } - } - - if (constantsWithValue.isEmpty()) { - constantsByValue.remove(withValue); - } - - return changed; - } - /** * Remove moves that are not actually used by instructions in exiting paths. These moves can arise * due to debug local info needing a particular value and the live-interval for it then moves it From 9d9822a30a9cd026be688f49ad60ba9113e5ac38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Mon, 19 Jun 2023 11:06:54 +0200 Subject: [PATCH 135/153] Missing override Change-Id: Iddf0741b06b4d26063225151a4e130062edeec80 --- .../android/tools/r8/ir/conversion/passes/BranchSimplifier.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index b39431b805..8ed518cdcd 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -173,6 +173,7 @@ private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySi this.anySimplifications = anySimplifications; } + @Override public ControlFlowSimplificationResult asControlFlowSimplificationResult() { return this; } From 57693cc9d715327ab0b44889fcb05f8637f7c442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Mon, 19 Jun 2023 12:38:33 +0200 Subject: [PATCH 136/153] Revert "Make ThrowCatchOptimizer a CodeRewriterPass" This reverts commit feab0c3389ac425d2ed6d3aa426445fb4edaac2b. Revert "BranchSimplifier as CodeRewriterPass" This reverts commit 3a0b2307ac573ffe3917da031aa3948084aded27. Revert "Split RedundantConstNumberRemover from CodeRewriter" This reverts commit 230da2811d55012970f0bdfe334f08f816b1b314. Revert "Missing override" This reverts commit 9d9822a30a9cd026be688f49ad60ba9113e5ac38. Change-Id: Iabdda4211ff8c17fdc07789620040e4342fe8f17 --- .../tools/r8/ir/conversion/IRConverter.java | 26 +- .../conversion/passes/BranchSimplifier.java | 108 ++----- .../passes/RedundantConstNumberRemover.java | 278 ------------------ .../passes/SwitchCaseEliminator.java | 6 +- .../passes/ThrowCatchOptimizer.java | 42 +-- .../passes/result/CodeRewriterResult.java | 5 - .../tools/r8/ir/optimize/CodeRewriter.java | 243 +++++++++++++++ .../r8/ir/optimize/ConstantCanonicalizer.java | 3 +- .../tools/r8/ir/optimize/DeadCodeRemover.java | 6 +- .../optimize/classinliner/ClassInliner.java | 2 +- .../shaking/EnqueuerDeferredTracingImpl.java | 2 +- 11 files changed, 304 insertions(+), 417 deletions(-) delete mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 994d45bea8..894117603a 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -36,7 +36,6 @@ import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.ir.conversion.passes.NaturalIntLoopRemover; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; -import com.android.tools.r8.ir.conversion.passes.RedundantConstNumberRemover; import com.android.tools.r8.ir.conversion.passes.SplitBranch; import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer; import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; @@ -761,16 +760,21 @@ Timing optimize( new StringBuilderAppendOptimizer(appView).run(code, timing); } new SparseConditionalConstantPropagation(appView, code).run(code, timing); - new ThrowCatchOptimizer(appView, isDebugMode).run(code, timing); - if (new BranchSimplifier(appView) - .run(code, timing) - .asControlFlowSimplificationResult() - .anyAffectedValues()) { + timing.begin("Rewrite always throwing instructions"); + new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(code); + timing.end(); + timing.begin("Simplify control flow"); + if (new BranchSimplifier(appView).simplifyBranches(code)) { new TrivialCheckCastAndInstanceOfRemover(appView) .run(code, methodProcessor, methodProcessingContext, timing); } + timing.end(); splitBranch.run(code, timing); - new RedundantConstNumberRemover(appView).run(code, timing); + if (options.enableRedundantConstNumberOptimization) { + timing.begin("Remove const numbers"); + codeRewriter.redundantConstNumberRemoval(code); + timing.end(); + } if (RedundantFieldLoadAndStoreElimination.shouldRun(appView, code)) { timing.begin("Remove field loads"); new RedundantFieldLoadAndStoreElimination(appView, code).run(); @@ -784,6 +788,13 @@ Timing optimize( invertConditionalsForTesting(code); } + if (!isDebugMode) { + timing.begin("Rewrite throw NPE"); + new ThrowCatchOptimizer(appView).rewriteThrowNullPointerException(code); + timing.end(); + previous = printMethod(code, "IR after rewrite throw null (SSA)", previous); + } + timing.begin("Optimize class initializers"); ClassInitializerDefaultsResult classInitializerDefaultsResult = classInitializerDefaultsOptimization.optimize(code, feedback); @@ -923,6 +934,7 @@ Timing optimize( // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL. assert code.verifyNoNullabilityBottomTypes(); + assert code.verifyTypes(appView); previous = diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index 8ed518cdcd..28ddb0119a 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -4,10 +4,6 @@ package com.android.tools.r8.ir.conversion.passes; -import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.NO_CHANGE; -import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.create; - -import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; @@ -39,10 +35,10 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.code.Xor; -import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOutputMode; import com.android.tools.r8.utils.LongInterval; import com.google.common.collect.ImmutableList; @@ -63,36 +59,23 @@ import java.util.PriorityQueue; import java.util.Set; -public class BranchSimplifier extends CodeRewriterPass { - - public BranchSimplifier(AppView appView) { - super(appView); - } - - @Override - protected String getTimingId() { - return "BranchSimplifier"; - } +public class BranchSimplifier { - @Override - protected boolean shouldRewriteCode(IRCode code) { - return true; - } + private final AppView appView; + private final InternalOptions options; - @Override - protected CodeRewriterResult rewriteCode(IRCode code) { - ControlFlowSimplificationResult switchResult = rewriteSwitch(code); - ControlFlowSimplificationResult ifResult = simplifyIf(code); - return switchResult.combine(ifResult); + public BranchSimplifier(AppView appView) { + this.appView = appView; + this.options = appView.options(); } - public ControlFlowSimplificationResult simplifyBranches(IRCode code) { - ControlFlowSimplificationResult switchResult = rewriteSwitch(code); - ControlFlowSimplificationResult ifResult = simplifyIf(code); - return switchResult.combine(ifResult); + public boolean simplifyBranches(IRCode code) { + boolean anyAffectedValues = rewriteSwitch(code); + anyAffectedValues |= simplifyIf(code).anyAffectedValues(); + return anyAffectedValues; } - private ControlFlowSimplificationResult simplifyIf(IRCode code) { + public ControlFlowSimplificationResult simplifyIf(IRCode code) { BasicBlockBehavioralSubsumption behavioralSubsumption = new BasicBlockBehavioralSubsumption(appView, code); boolean simplified = false; @@ -143,26 +126,10 @@ private ControlFlowSimplificationResult simplifyIf(IRCode code) { } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - return create(!affectedValues.isEmpty(), simplified); + return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified); } - public static class ControlFlowSimplificationResult implements CodeRewriterResult { - - static ControlFlowSimplificationResult create( - boolean anyAffectedValues, boolean anySimplifications) { - if (anyAffectedValues) { - assert anySimplifications; - return ALL_CHANGED; - } - return anySimplifications ? ONLY_SIMPLIFICATIONS : NO_CHANGE; - } - - static final ControlFlowSimplificationResult ALL_CHANGED = - new ControlFlowSimplificationResult(true, true); - static final ControlFlowSimplificationResult ONLY_SIMPLIFICATIONS = - new ControlFlowSimplificationResult(false, true); - static final ControlFlowSimplificationResult NO_CHANGE = - new ControlFlowSimplificationResult(false, false); + public static class ControlFlowSimplificationResult { private final boolean anyAffectedValues; private final boolean anySimplifications; @@ -173,11 +140,6 @@ private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySi this.anySimplifications = anySimplifications; } - @Override - public ControlFlowSimplificationResult asControlFlowSimplificationResult() { - return this; - } - public boolean anyAffectedValues() { return anyAffectedValues; } @@ -185,18 +147,6 @@ public boolean anyAffectedValues() { public boolean anySimplifications() { return anySimplifications; } - - @Override - public boolean hasChanged() { - assert !anyAffectedValues || anySimplifications; - return anySimplifications(); - } - - public ControlFlowSimplificationResult combine(ControlFlowSimplificationResult ifResult) { - return create( - anyAffectedValues || ifResult.anyAffectedValues, - anySimplifications || ifResult.anySimplifications); - } } private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) { @@ -617,25 +567,22 @@ private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) { return true; } - private ControlFlowSimplificationResult rewriteSwitch(IRCode code) { + private boolean rewriteSwitch(IRCode code) { return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance()); } - private ControlFlowSimplificationResult rewriteSwitch( - IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { if (!options.isSwitchRewritingEnabled()) { - return NO_CHANGE; + return false; } if (!code.metadata().mayHaveSwitch()) { - return NO_CHANGE; + return false; } return rewriteSwitchFull(code, switchCaseAnalyzer); } - private ControlFlowSimplificationResult rewriteSwitchFull( - IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { boolean needToRemoveUnreachableBlocks = false; - boolean anySimplifications = false; ListIterator blocksIterator = code.listIterator(); while (blocksIterator.hasNext()) { BasicBlock block = blocksIterator.next(); @@ -647,7 +594,6 @@ private ControlFlowSimplificationResult rewriteSwitchFull( if (options.testing.enableDeadSwitchCaseElimination) { SwitchCaseEliminator eliminator = removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer); - anySimplifications |= eliminator.canBeOptimized(); if (eliminator.mayHaveIntroducedUnreachableBlocks()) { needToRemoveUnreachableBlocks = true; } @@ -662,8 +608,7 @@ private ControlFlowSimplificationResult rewriteSwitchFull( theSwitch = instruction.asSwitch(); } if (theSwitch.isIntSwitch()) { - anySimplifications |= - rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); + rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); } } } @@ -676,13 +621,12 @@ private ControlFlowSimplificationResult rewriteSwitchFull( Set affectedValues = needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of(); - boolean anyAffectedValues = !affectedValues.isEmpty(); - if (anyAffectedValues) { + if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - return create(anyAffectedValues, anySimplifications); + return !affectedValues.isEmpty(); } public void rewriteSingleKeySwitchToIf( @@ -708,19 +652,19 @@ public void rewriteSingleKeySwitchToIf( iterator.replaceCurrentInstruction(replacement); } - private boolean rewriteIntSwitch( + private void rewriteIntSwitch( IRCode code, ListIterator blockIterator, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) { if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) { - return false; + return; } if (theSwitch.numberOfKeys() == 1) { rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch); - return true; + return; } // If there are more than 1 key, we use the following algorithm to find keys to combine. @@ -809,9 +753,7 @@ private boolean rewriteIntSwitch( if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) { convertSwitchToSwitchAndIfs( code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers); - return true; } - return false; } // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java deleted file mode 100644 index 70402ccd47..0000000000 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) 2023, 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.ir.conversion.passes; - -import com.android.tools.r8.graph.AppInfo; -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.ConstNumber; -import com.android.tools.r8.ir.code.DominatorTree; -import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.If; -import com.android.tools.r8.ir.code.IfType; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; -import com.android.tools.r8.utils.LazyBox; -import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; -import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - -/** - * This optimization exploits that we can sometimes learn the constant value of an SSA value that - * flows into an if-eq of if-neq instruction. - * - *

Consider the following example: - * - *

- * 1. if (obj != null) {
- * 2.  return doStuff();
- * 3. }
- * 4. return null;
- * 
- * - *

Since we know that `obj` is null in all blocks that are dominated by the false-target of the - * if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and thereby - * save a const-number instruction. - */ -public class RedundantConstNumberRemover extends CodeRewriterPass { - - public RedundantConstNumberRemover(AppView appView) { - super(appView); - } - - @Override - protected String getTimingId() { - return "RedundantConstNumberRemover"; - } - - @Override - protected boolean shouldRewriteCode(IRCode code) { - return options.enableRedundantConstNumberOptimization && code.metadata().mayHaveConstNumber(); - } - - @Override - protected CodeRewriterResult rewriteCode(IRCode code) { - redundantConstNumberRemoval(code); - return CodeRewriterResult.NONE; - } - - public void redundantConstNumberRemoval(IRCode code) { - if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() - && !appView.options().testing.forceRedundantConstNumberRemoval) { - // See also b/124152497. - return; - } - - LazyBox>> constantsByValue = - new LazyBox<>(() -> getConstantsByValue(code)); - LazyBox dominatorTree = new LazyBox<>(() -> new DominatorTree(code)); - - boolean changed = false; - for (BasicBlock block : code.blocks) { - Instruction lastInstruction = block.getInstructions().getLast(); - if (!lastInstruction.isIf()) { - continue; - } - - If ifInstruction = lastInstruction.asIf(); - IfType type = ifInstruction.getType(); - - Value lhs = ifInstruction.inValues().get(0); - Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null; - - if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) { - // We can only conclude anything from an if-instruction if it is a zero-test or if one of - // the two operands is a constant. - continue; - } - - // If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values - // of the if-instruction from the outcome of the if-instruction. - if (type != IfType.EQ && type != IfType.NE) { - continue; - } - - BasicBlock trueTarget, falseTarget; - if (type == IfType.EQ) { - trueTarget = ifInstruction.getTrueTarget(); - falseTarget = ifInstruction.fallthroughBlock(); - } else { - falseTarget = ifInstruction.getTrueTarget(); - trueTarget = ifInstruction.fallthroughBlock(); - } - - if (ifInstruction.isZeroTest()) { - changed |= - replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree); - if (lhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - 1, lhs, falseTarget, constantsByValue, code, dominatorTree); - } - } else { - assert rhs != null; - if (lhs.isConstNumber()) { - ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber(); - changed |= - replaceDominatedConstNumbers( - lhsAsNumber.getRawValue(), - rhs, - trueTarget, - constantsByValue, - code, - dominatorTree); - if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - negateBoolean(lhsAsNumber), - rhs, - falseTarget, - constantsByValue, - code, - dominatorTree); - } - } else { - assert rhs.isConstNumber(); - ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber(); - changed |= - replaceDominatedConstNumbers( - rhsAsNumber.getRawValue(), - lhs, - trueTarget, - constantsByValue, - code, - dominatorTree); - if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - negateBoolean(rhsAsNumber), - lhs, - falseTarget, - constantsByValue, - code, - dominatorTree); - } - } - } - - if (constantsByValue.computeIfAbsent().isEmpty()) { - break; - } - } - - if (changed) { - code.removeAllDeadAndTrivialPhis(); - } - assert code.isConsistentSSA(appView); - } - - private static Long2ReferenceMap> getConstantsByValue(IRCode code) { - // A map from the raw value of constants in `code` to the const number instructions that define - // the given raw value (irrespective of the type of the raw value). - Long2ReferenceMap> constantsByValue = new Long2ReferenceOpenHashMap<>(); - - // Initialize `constantsByValue`. - for (Instruction instruction : code.instructions()) { - if (instruction.isConstNumber()) { - ConstNumber constNumber = instruction.asConstNumber(); - if (constNumber.outValue().hasLocalInfo()) { - // Not necessarily constant, because it could be changed in the debugger. - continue; - } - long rawValue = constNumber.getRawValue(); - if (constantsByValue.containsKey(rawValue)) { - constantsByValue.get(rawValue).add(constNumber); - } else { - List list = new ArrayList<>(); - list.add(constNumber); - constantsByValue.put(rawValue, list); - } - } - } - return constantsByValue; - } - - private static int negateBoolean(ConstNumber number) { - assert number.outValue().knownToBeBoolean(); - return number.getRawValue() == 0 ? 1 : 0; - } - - private boolean replaceDominatedConstNumbers( - long withValue, - Value newValue, - BasicBlock dominator, - LazyBox>> constantsByValueSupplier, - IRCode code, - LazyBox dominatorTree) { - if (newValue.hasLocalInfo()) { - // We cannot replace a constant with a value that has local info, because that could change - // debugging behavior. - return false; - } - - Long2ReferenceMap> constantsByValue = - constantsByValueSupplier.computeIfAbsent(); - List constantsWithValue = constantsByValue.get(withValue); - if (constantsWithValue == null || constantsWithValue.isEmpty()) { - return false; - } - - boolean changed = false; - - ListIterator constantWithValueIterator = constantsWithValue.listIterator(); - while (constantWithValueIterator.hasNext()) { - ConstNumber constNumber = constantWithValueIterator.next(); - Value value = constNumber.outValue(); - assert !value.hasLocalInfo(); - assert constNumber.getRawValue() == withValue; - - BasicBlock block = constNumber.getBlock(); - - // If the following condition does not hold, then the if-instruction does not dominate the - // block containing the constant, although the true or false target does. - if (block == dominator && block.getPredecessors().size() != 1) { - // This should generally not happen, but it is possible to write bytecode where it does. - assert false; - continue; - } - - if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) { - // We cannot replace a boolean by a none-boolean since that can lead to verification - // errors. For example, the following code fails with "register v1 has type Imprecise - // Constant: 127 but expected Boolean return-1nr". - // - // public boolean convertIntToBoolean(int v1) { - // const/4 v0, 0x1 - // if-eq v1, v0, :eq_true - // const/4 v1, 0x0 - // :eq_true - // return v1 - // } - continue; - } - - if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) { - if (newValue.getType().lessThanOrEqual(value.getType(), appView)) { - value.replaceUsers(newValue); - block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead(); - constantWithValueIterator.remove(); - changed = true; - } else if (value.getType().isNullType()) { - // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of - // the use sites of `value` without introducing a type error. - } - } - } - - if (constantsWithValue.isEmpty()) { - constantsByValue.remove(withValue); - } - - return changed; - } -} diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java index 4a1a1d4e7b..cbda6309d0 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java @@ -45,7 +45,7 @@ private boolean allSwitchCasesMarkedForRemoval() { && switchCasesToBeRemoved.size() == theSwitch.numberOfKeys(); } - boolean canBeOptimized() { + private boolean canBeOptimized() { assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty(); return switchCasesToBeRemoved != null || hasAlwaysHitCase() || !isFallthroughLive(); } @@ -96,7 +96,7 @@ void markSwitchFallthroughAsNeverHit() { liveFallthrough = false; } - void optimize() { + boolean optimize() { if (canBeOptimized()) { int originalNumberOfSuccessors = block.getSuccessors().size(); IntList removedSuccessorIndices = unlinkDeadSuccessors(); @@ -107,7 +107,9 @@ void optimize() { // Replace switch by a new switch where the dead switch cases have been removed. replaceSwitchByOptimizedSwitch(originalNumberOfSuccessors, removedSuccessorIndices); } + return true; } + return false; } private IntList unlinkDeadSuccessors() { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java index f5a684130b..f34a6bd20e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java @@ -5,10 +5,10 @@ package com.android.tools.r8.ir.conversion.passes; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; @@ -32,7 +32,6 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -41,41 +40,18 @@ import java.util.ListIterator; import java.util.Set; -public class ThrowCatchOptimizer extends CodeRewriterPass { +public class ThrowCatchOptimizer { - private final boolean rewriteThrowNull; - - public ThrowCatchOptimizer(AppView appView, boolean isDebug) { - super(appView); - this.rewriteThrowNull = !isDebug; - } + private final AppView appView; + private final DexItemFactory dexItemFactory; public ThrowCatchOptimizer(AppView appView) { - super(appView); - this.rewriteThrowNull = false; - } - - @Override - protected String getTimingId() { - return "ThrowCatchOptimizer"; - } - - @Override - protected boolean shouldRewriteCode(IRCode code) { - return true; - } - - @Override - protected CodeRewriterResult rewriteCode(IRCode code) { - optimizeAlwaysThrowingInstructions(code); - if (rewriteThrowNull) { - rewriteThrowNullPointerException(code); - } - return CodeRewriterResult.NONE; + this.appView = appView; + this.dexItemFactory = appView.dexItemFactory(); } // Rewrite 'throw new NullPointerException()' to 'throw null'. - private void rewriteThrowNullPointerException(IRCode code) { + public void rewriteThrowNullPointerException(IRCode code) { boolean shouldRemoveUnreachableBlocks = false; for (BasicBlock block : code.blocks) { InstructionListIterator it = block.listIterator(code); @@ -88,7 +64,7 @@ private void rewriteThrowNullPointerException(IRCode code) { if (appView .dexItemFactory() .objectsMethods - .isRequireNonNullMethod(code.context().getReference())) { + .isRequireNonNullMethod(code.method().getReference())) { continue; } @@ -216,7 +192,7 @@ private void rewriteThrowNullPointerException(IRCode code) { // Find all instructions that always throw, split the block after each such instruction and follow // it with a block throwing a null value (which should result in NPE). Note that this throw is not // expected to be ever reached, but is intended to satisfy verifier. - private void optimizeAlwaysThrowingInstructions(IRCode code) { + public void optimizeAlwaysThrowingInstructions(IRCode code) { Set affectedValues = Sets.newIdentityHashSet(); Set blocksToRemove = Sets.newIdentityHashSet(); ListIterator blockIterator = code.listIterator(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java index 1af288a003..68d5c0d2ff 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java @@ -5,7 +5,6 @@ package com.android.tools.r8.ir.conversion.passes.result; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult; public interface CodeRewriterResult { @@ -38,8 +37,4 @@ public boolean hasChanged() { } boolean hasChanged(); - - default ControlFlowSimplificationResult asControlFlowSimplificationResult() { - throw new Unreachable("Not a control flow simplification result."); - } } 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 19f053fa46..e6968798e7 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 @@ -21,9 +21,11 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; import com.android.tools.r8.ir.code.DebugLocalWrite; import com.android.tools.r8.ir.code.DebugLocalsChange; +import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; @@ -38,6 +40,7 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; +import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.common.collect.Streams; @@ -48,7 +51,11 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import java.util.ArrayList; import java.util.List; +import java.util.ListIterator; import java.util.Set; public class CodeRewriter { @@ -202,6 +209,242 @@ public void simplifyDebugLocals(IRCode code) { } } + /** + * This optimization exploits that we can sometimes learn the constant value of an SSA value that + * flows into an if-eq of if-neq instruction. + * + *

Consider the following example: + * + *

+   * 1. if (obj != null) {
+   * 2.  return doStuff();
+   * 3. }
+   * 4. return null;
+   * 
+ * + *

Since we know that `obj` is null in all blocks that are dominated by the false-target of the + * if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and + * thereby save a const-number instruction. + */ + public void redundantConstNumberRemoval(IRCode code) { + if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() + && !appView.options().testing.forceRedundantConstNumberRemoval) { + // See also b/124152497. + return; + } + + if (!code.metadata().mayHaveConstNumber()) { + return; + } + + LazyBox>> constantsByValue = + new LazyBox<>(() -> getConstantsByValue(code)); + LazyBox dominatorTree = new LazyBox<>(() -> new DominatorTree(code)); + + boolean changed = false; + for (BasicBlock block : code.blocks) { + Instruction lastInstruction = block.getInstructions().getLast(); + if (!lastInstruction.isIf()) { + continue; + } + + If ifInstruction = lastInstruction.asIf(); + IfType type = ifInstruction.getType(); + + Value lhs = ifInstruction.inValues().get(0); + Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null; + + if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) { + // We can only conclude anything from an if-instruction if it is a zero-test or if one of + // the two operands is a constant. + continue; + } + + // If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values + // of the if-instruction from the outcome of the if-instruction. + if (type != IfType.EQ && type != IfType.NE) { + continue; + } + + BasicBlock trueTarget, falseTarget; + if (type == IfType.EQ) { + trueTarget = ifInstruction.getTrueTarget(); + falseTarget = ifInstruction.fallthroughBlock(); + } else { + falseTarget = ifInstruction.getTrueTarget(); + trueTarget = ifInstruction.fallthroughBlock(); + } + + if (ifInstruction.isZeroTest()) { + changed |= + replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree); + if (lhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + 1, lhs, falseTarget, constantsByValue, code, dominatorTree); + } + } else { + assert rhs != null; + if (lhs.isConstNumber()) { + ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber(); + changed |= + replaceDominatedConstNumbers( + lhsAsNumber.getRawValue(), + rhs, + trueTarget, + constantsByValue, + code, + dominatorTree); + if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + negateBoolean(lhsAsNumber), + rhs, + falseTarget, + constantsByValue, + code, + dominatorTree); + } + } else { + assert rhs.isConstNumber(); + ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber(); + changed |= + replaceDominatedConstNumbers( + rhsAsNumber.getRawValue(), + lhs, + trueTarget, + constantsByValue, + code, + dominatorTree); + if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + negateBoolean(rhsAsNumber), + lhs, + falseTarget, + constantsByValue, + code, + dominatorTree); + } + } + } + + if (constantsByValue.computeIfAbsent().isEmpty()) { + break; + } + } + + if (changed) { + code.removeAllDeadAndTrivialPhis(); + } + assert code.isConsistentSSA(appView); + } + + private static Long2ReferenceMap> getConstantsByValue(IRCode code) { + // A map from the raw value of constants in `code` to the const number instructions that define + // the given raw value (irrespective of the type of the raw value). + Long2ReferenceMap> constantsByValue = new Long2ReferenceOpenHashMap<>(); + + // Initialize `constantsByValue`. + for (Instruction instruction : code.instructions()) { + if (instruction.isConstNumber()) { + ConstNumber constNumber = instruction.asConstNumber(); + if (constNumber.outValue().hasLocalInfo()) { + // Not necessarily constant, because it could be changed in the debugger. + continue; + } + long rawValue = constNumber.getRawValue(); + if (constantsByValue.containsKey(rawValue)) { + constantsByValue.get(rawValue).add(constNumber); + } else { + List list = new ArrayList<>(); + list.add(constNumber); + constantsByValue.put(rawValue, list); + } + } + } + return constantsByValue; + } + + private static int negateBoolean(ConstNumber number) { + assert number.outValue().knownToBeBoolean(); + return number.getRawValue() == 0 ? 1 : 0; + } + + private boolean replaceDominatedConstNumbers( + long withValue, + Value newValue, + BasicBlock dominator, + LazyBox>> constantsByValueSupplier, + IRCode code, + LazyBox dominatorTree) { + if (newValue.hasLocalInfo()) { + // We cannot replace a constant with a value that has local info, because that could change + // debugging behavior. + return false; + } + + Long2ReferenceMap> constantsByValue = + constantsByValueSupplier.computeIfAbsent(); + List constantsWithValue = constantsByValue.get(withValue); + if (constantsWithValue == null || constantsWithValue.isEmpty()) { + return false; + } + + boolean changed = false; + + ListIterator constantWithValueIterator = constantsWithValue.listIterator(); + while (constantWithValueIterator.hasNext()) { + ConstNumber constNumber = constantWithValueIterator.next(); + Value value = constNumber.outValue(); + assert !value.hasLocalInfo(); + assert constNumber.getRawValue() == withValue; + + BasicBlock block = constNumber.getBlock(); + + // If the following condition does not hold, then the if-instruction does not dominate the + // block containing the constant, although the true or false target does. + if (block == dominator && block.getPredecessors().size() != 1) { + // This should generally not happen, but it is possible to write bytecode where it does. + assert false; + continue; + } + + if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) { + // We cannot replace a boolean by a none-boolean since that can lead to verification + // errors. For example, the following code fails with "register v1 has type Imprecise + // Constant: 127 but expected Boolean return-1nr". + // + // public boolean convertIntToBoolean(int v1) { + // const/4 v0, 0x1 + // if-eq v1, v0, :eq_true + // const/4 v1, 0x0 + // :eq_true + // return v1 + // } + continue; + } + + if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) { + if (newValue.getType().lessThanOrEqual(value.getType(), appView)) { + value.replaceUsers(newValue); + block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead(); + constantWithValueIterator.remove(); + changed = true; + } else if (value.getType().isNullType()) { + // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of + // the use sites of `value` without introducing a type error. + } + } + } + + if (constantsWithValue.isEmpty()) { + constantsByValue.remove(withValue); + } + + return changed; + } + /** * Remove moves that are not actually used by instructions in exiting paths. These moves can arise * due to debug local info needing a particular value and the live-interval for it then moves it diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java index 13f049be27..c7ddc680e5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java @@ -43,7 +43,6 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; import com.android.tools.r8.utils.OptionalBool; -import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.Hash.Strategy; @@ -289,7 +288,7 @@ public boolean equals(Instruction a, Instruction b) { shouldSimplifyIfs |= code.removeAllDeadAndTrivialPhis(); if (shouldSimplifyIfs) { - branchSimplifier.run(code, Timing.empty()); + branchSimplifier.simplifyIf(code); } code.removeRedundantBlocks(); diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index c6cbe68e56..e9b180c578 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java @@ -60,15 +60,11 @@ public void run(IRCode code, Timing timing) { removeDeadInstructions(worklist, code, block, valueIsDeadAnalysis); removeDeadPhis(worklist, block, valueIsDeadAnalysis); } - } while (branchSimplifier - .simplifyBranches(code) - .asControlFlowSimplificationResult() - .anySimplifications() + } while (branchSimplifier.simplifyIf(code).anySimplifications() || removeUnneededCatchHandlers(code)); code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - assert verifyNoDeadCode(code); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index b46d0a5bfa..d3d2a08bff 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -252,7 +252,7 @@ public final void processMethodCode( new TrivialCheckCastAndInstanceOfRemover(appView) .run(code, methodProcessor, methodProcessingContext, Timing.empty()); // If a method was inlined we may be able to prune additional branches. - new BranchSimplifier(appView).run(code, Timing.empty()); + new BranchSimplifier(appView).simplifyBranches(code); // If a method was inlined we may see more trivial computation/conversion of String. boolean isDebugMode = appView.options().debug || method.getOrComputeReachabilitySensitive(appView); diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java index f7cf0b5a21..1c0d3de9d7 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java @@ -270,7 +270,7 @@ private void rewriteMethod( rewriter.rewriteCode(ir, initializedClassesWithContexts, prunedFields); // Run dead code elimination. - new ThrowCatchOptimizer(appView).run(ir, Timing.empty()); + new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(ir); rewriter.getDeadCodeRemover().run(ir, Timing.empty()); // Finalize to class files or dex. From 086a2c53f476e523ec553172337538f15e287963 Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Mon, 19 Jun 2023 13:42:58 +0200 Subject: [PATCH 137/153] Add bot running with cached art invocations Bug: 286019067 Change-Id: I6d36816f92573aac22639da0409209a3d158501c --- .../global/generated/cr-buildbucket.cfg | 32 +++++++++++++++++++ infra/config/global/generated/luci-milo.cfg | 5 +++ infra/config/global/generated/luci-notify.cfg | 12 +++++++ .../global/generated/luci-scheduler.cfg | 16 ++++++++++ infra/config/global/main.star | 20 ++++++++++++ 5 files changed, 85 insertions(+) diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg index 661516d9df..b41bd378af 100644 --- a/infra/config/global/generated/cr-buildbucket.cfg +++ b/infra/config/global/generated/cr-buildbucket.cfg @@ -76,6 +76,38 @@ buckets { value: 100 } } + builders { + name: "cached" + swarming_host: "chrome-swarming.appspot.com" + swarming_tags: "vpython:native-python-wrapper" + dimensions: "cores:8" + dimensions: "cpu:x86-64" + dimensions: "os:Ubuntu-20.04" + dimensions: "pool:luci.r8.ci" + exe { + cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" + cipd_version: "refs/heads/master" + cmd: "luciexe" + } + properties: + '{' + ' "builder_group": "internal.client.r8",' + ' "recipe": "rex",' + ' "test_options": [' + ' "--runtimes=dex-default",' + ' "--command_cache_dir=/tmp/ccache"' + ' ]' + '}' + priority: 25 + execution_timeout_secs: 21600 + expiration_secs: 126000 + build_numbers: YES + service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + } builders { name: "check" swarming_host: "chrome-swarming.appspot.com" diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg index 8914541570..b0f25460a0 100644 --- a/infra/config/global/generated/luci-milo.cfg +++ b/infra/config/global/generated/luci-milo.cfg @@ -20,6 +20,11 @@ consoles { category: "archive" short_name: "check" } + builders { + name: "buildbucket/luci.r8.ci/cached" + category: "R8" + short_name: "cached" + } builders { name: "buildbucket/luci.r8.ci/linux-dex_default" category: "R8" diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg index 521dee428a..048dc7a53c 100644 --- a/infra/config/global/generated/luci-notify.cfg +++ b/infra/config/global/generated/luci-notify.cfg @@ -28,6 +28,18 @@ notifiers { repository: "https://r8.googlesource.com/r8" } } +notifiers { + notifications { + on_failure: true + on_new_failure: true + notify_blamelist {} + } + builders { + bucket: "ci" + name: "cached" + repository: "https://r8.googlesource.com/r8" + } +} notifiers { notifications { on_failure: true diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg index f990fdb89a..03691dd3ca 100644 --- a/infra/config/global/generated/luci-scheduler.cfg +++ b/infra/config/global/generated/luci-scheduler.cfg @@ -34,6 +34,21 @@ job { builder: "archive_release" } } +job { + id: "cached" + realm: "ci" + acl_sets: "ci" + triggering_policy { + kind: GREEDY_BATCHING + max_concurrent_invocations: 1 + max_batch_size: 1 + } + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "ci" + builder: "cached" + } +} job { id: "check" realm: "ci" @@ -812,6 +827,7 @@ trigger { realm: "ci" acl_sets: "ci" triggers: "archive" + triggers: "cached" triggers: "check" triggers: "desugared_library-head" triggers: "desugared_library-jdk11_head" diff --git a/infra/config/global/main.star b/infra/config/global/main.star index c2ce866146..219314351a 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star @@ -301,6 +301,26 @@ r8_builder( expiration_timeout = time.hour * 35, ) +r8_builder( + "cached", + category = "R8", + dimensions = get_dimensions(), + triggering_policy = scheduler.policy( + kind = scheduler.GREEDY_BATCHING_KIND, + max_batch_size = 1, + max_concurrent_invocations = 1 + ), + priority = 25, + properties = { + "test_options" : ["--runtimes=dex-default", "--command_cache_dir=/tmp/ccache"], + "builder_group" : "internal.client.r8" + }, + execution_timeout = time.hour * 6, + expiration_timeout = time.hour * 35, +) + + + r8_tester_with_default("linux-dex_default", ["--runtimes=dex-default"]) r8_tester_with_default("linux-none", ["--runtimes=none"]) r8_tester_with_default("linux-jdk8", ["--runtimes=jdk8"]) From 676246e3e126b8a6b272a5d7db55b535585b25a4 Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Mon, 19 Jun 2023 09:47:29 +0200 Subject: [PATCH 138/153] Update comments on AndroidResourceInput to document proto requirement Bug: b/287399385 Change-Id: I761c3103df4c6456c89e3150f603e4eb81ba5976 --- .../java/com/android/tools/r8/AndroidResourceInput.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/android/tools/r8/AndroidResourceInput.java b/src/main/java/com/android/tools/r8/AndroidResourceInput.java index 00f61c795d..87ed346a66 100644 --- a/src/main/java/com/android/tools/r8/AndroidResourceInput.java +++ b/src/main/java/com/android/tools/r8/AndroidResourceInput.java @@ -16,11 +16,12 @@ @Keep public interface AndroidResourceInput extends Resource { enum Kind { - // The AndroidManifest.xml file. + // The AndroidManifest.xml file in proto format. MANIFEST, - // The resource table, in either binary or proto format. + // The resource table, in proto format. RESOURCE_TABLE, - // An xml file within the res folder. + // An xml file within the res folder, in proto format if not inside res/raw, otherwise + // in UTF-8 format. XML_FILE, // Any other binary file withing the res folder. RES_FOLDER_FILE From d94b8b458cf8f712c9f4598dfd6e174847fb4c91 Mon Sep 17 00:00:00 2001 From: Rico Wind Date: Mon, 19 Jun 2023 15:12:09 +0200 Subject: [PATCH 139/153] Ensure that we pass --no-internal on cached bot Also, be consistent and archive failures and print one line per test. Bug: b/286019067 Change-Id: If51f1d383c5064e165d52a98b71d336a204c9954 --- infra/config/global/generated/cr-buildbucket.cfg | 6 +++++- infra/config/global/main.star | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg index b41bd378af..5c3f1b9338 100644 --- a/infra/config/global/generated/cr-buildbucket.cfg +++ b/infra/config/global/generated/cr-buildbucket.cfg @@ -95,7 +95,11 @@ buckets { ' "recipe": "rex",' ' "test_options": [' ' "--runtimes=dex-default",' - ' "--command_cache_dir=/tmp/ccache"' + ' "--command_cache_dir=/tmp/ccache",' + ' "--tool=r8",' + ' "--no_internal",' + ' "--one_line_per_test",' + ' "--archive_failures"' ' ]' '}' priority: 25 diff --git a/infra/config/global/main.star b/infra/config/global/main.star index 219314351a..358def36ca 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star @@ -312,7 +312,8 @@ r8_builder( ), priority = 25, properties = { - "test_options" : ["--runtimes=dex-default", "--command_cache_dir=/tmp/ccache"], + "test_options" : ["--runtimes=dex-default", + "--command_cache_dir=/tmp/ccache"] + common_test_options, "builder_group" : "internal.client.r8" }, execution_timeout = time.hour * 6, From d0a1faaa12bdef153d0cef569cdd9cc6fd7af6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C3=A9ra?= Date: Mon, 19 Jun 2023 14:51:08 +0200 Subject: [PATCH 140/153] Reland "Make ThrowCatchOptimizer a CodeRewriterPass" This reverts commit 57693cc9d715327ab0b44889fcb05f8637f7c442. Change-Id: I5ba37246f42f5e563db57e1db13510c01158b0e5 --- .../tools/r8/ir/conversion/IRConverter.java | 26 +- .../conversion/passes/BranchSimplifier.java | 100 +++++-- .../passes/RedundantConstNumberRemover.java | 278 ++++++++++++++++++ .../passes/SwitchCaseEliminator.java | 6 +- .../passes/ThrowCatchOptimizer.java | 48 ++- .../passes/result/CodeRewriterResult.java | 5 + .../tools/r8/ir/optimize/CodeRewriter.java | 243 --------------- .../tools/r8/ir/optimize/DeadCodeRemover.java | 6 +- .../optimize/classinliner/ClassInliner.java | 2 +- .../shaking/EnqueuerDeferredTracingImpl.java | 2 +- 10 files changed, 414 insertions(+), 302 deletions(-) create mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 894117603a..994d45bea8 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -36,6 +36,7 @@ import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.ir.conversion.passes.NaturalIntLoopRemover; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; +import com.android.tools.r8.ir.conversion.passes.RedundantConstNumberRemover; import com.android.tools.r8.ir.conversion.passes.SplitBranch; import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer; import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; @@ -760,21 +761,16 @@ Timing optimize( new StringBuilderAppendOptimizer(appView).run(code, timing); } new SparseConditionalConstantPropagation(appView, code).run(code, timing); - timing.begin("Rewrite always throwing instructions"); - new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(code); - timing.end(); - timing.begin("Simplify control flow"); - if (new BranchSimplifier(appView).simplifyBranches(code)) { + new ThrowCatchOptimizer(appView, isDebugMode).run(code, timing); + if (new BranchSimplifier(appView) + .run(code, timing) + .asControlFlowSimplificationResult() + .anyAffectedValues()) { new TrivialCheckCastAndInstanceOfRemover(appView) .run(code, methodProcessor, methodProcessingContext, timing); } - timing.end(); splitBranch.run(code, timing); - if (options.enableRedundantConstNumberOptimization) { - timing.begin("Remove const numbers"); - codeRewriter.redundantConstNumberRemoval(code); - timing.end(); - } + new RedundantConstNumberRemover(appView).run(code, timing); if (RedundantFieldLoadAndStoreElimination.shouldRun(appView, code)) { timing.begin("Remove field loads"); new RedundantFieldLoadAndStoreElimination(appView, code).run(); @@ -788,13 +784,6 @@ Timing optimize( invertConditionalsForTesting(code); } - if (!isDebugMode) { - timing.begin("Rewrite throw NPE"); - new ThrowCatchOptimizer(appView).rewriteThrowNullPointerException(code); - timing.end(); - previous = printMethod(code, "IR after rewrite throw null (SSA)", previous); - } - timing.begin("Optimize class initializers"); ClassInitializerDefaultsResult classInitializerDefaultsResult = classInitializerDefaultsOptimization.optimize(code, feedback); @@ -934,7 +923,6 @@ Timing optimize( // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL. assert code.verifyNoNullabilityBottomTypes(); - assert code.verifyTypes(appView); previous = diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index 28ddb0119a..44c9dddf15 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -4,6 +4,10 @@ package com.android.tools.r8.ir.conversion.passes; +import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.NO_CHANGE; +import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.create; + +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; @@ -35,10 +39,10 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.code.Xor; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOutputMode; import com.android.tools.r8.utils.LongInterval; import com.google.common.collect.ImmutableList; @@ -59,20 +63,27 @@ import java.util.PriorityQueue; import java.util.Set; -public class BranchSimplifier { - - private final AppView appView; - private final InternalOptions options; +public class BranchSimplifier extends CodeRewriterPass { public BranchSimplifier(AppView appView) { - this.appView = appView; - this.options = appView.options(); + super(appView); + } + + @Override + protected String getTimingId() { + return "BranchSimplifier"; } - public boolean simplifyBranches(IRCode code) { - boolean anyAffectedValues = rewriteSwitch(code); - anyAffectedValues |= simplifyIf(code).anyAffectedValues(); - return anyAffectedValues; + @Override + protected boolean shouldRewriteCode(IRCode code) { + return true; + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + ControlFlowSimplificationResult switchResult = rewriteSwitch(code); + ControlFlowSimplificationResult ifResult = simplifyIf(code); + return switchResult.combine(ifResult); } public ControlFlowSimplificationResult simplifyIf(IRCode code) { @@ -126,10 +137,26 @@ public ControlFlowSimplificationResult simplifyIf(IRCode code) { } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified); + return create(!affectedValues.isEmpty(), simplified); } - public static class ControlFlowSimplificationResult { + public static class ControlFlowSimplificationResult implements CodeRewriterResult { + + static ControlFlowSimplificationResult create( + boolean anyAffectedValues, boolean anySimplifications) { + if (anyAffectedValues) { + assert anySimplifications; + return ALL_CHANGED; + } + return anySimplifications ? ONLY_SIMPLIFICATIONS : NO_CHANGE; + } + + static final ControlFlowSimplificationResult ALL_CHANGED = + new ControlFlowSimplificationResult(true, true); + static final ControlFlowSimplificationResult ONLY_SIMPLIFICATIONS = + new ControlFlowSimplificationResult(false, true); + static final ControlFlowSimplificationResult NO_CHANGE = + new ControlFlowSimplificationResult(false, false); private final boolean anyAffectedValues; private final boolean anySimplifications; @@ -140,6 +167,11 @@ private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySi this.anySimplifications = anySimplifications; } + @Override + public ControlFlowSimplificationResult asControlFlowSimplificationResult() { + return this; + } + public boolean anyAffectedValues() { return anyAffectedValues; } @@ -147,6 +179,18 @@ public boolean anyAffectedValues() { public boolean anySimplifications() { return anySimplifications; } + + @Override + public boolean hasChanged() { + assert !anyAffectedValues || anySimplifications; + return anySimplifications(); + } + + public ControlFlowSimplificationResult combine(ControlFlowSimplificationResult ifResult) { + return create( + anyAffectedValues || ifResult.anyAffectedValues, + anySimplifications || ifResult.anySimplifications); + } } private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) { @@ -567,22 +611,25 @@ private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) { return true; } - private boolean rewriteSwitch(IRCode code) { + private ControlFlowSimplificationResult rewriteSwitch(IRCode code) { return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance()); } - private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + private ControlFlowSimplificationResult rewriteSwitch( + IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { if (!options.isSwitchRewritingEnabled()) { - return false; + return NO_CHANGE; } if (!code.metadata().mayHaveSwitch()) { - return false; + return NO_CHANGE; } return rewriteSwitchFull(code, switchCaseAnalyzer); } - private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + private ControlFlowSimplificationResult rewriteSwitchFull( + IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { boolean needToRemoveUnreachableBlocks = false; + boolean anySimplifications = false; ListIterator blocksIterator = code.listIterator(); while (blocksIterator.hasNext()) { BasicBlock block = blocksIterator.next(); @@ -594,6 +641,7 @@ private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnal if (options.testing.enableDeadSwitchCaseElimination) { SwitchCaseEliminator eliminator = removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer); + anySimplifications |= eliminator.canBeOptimized(); if (eliminator.mayHaveIntroducedUnreachableBlocks()) { needToRemoveUnreachableBlocks = true; } @@ -608,7 +656,8 @@ private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnal theSwitch = instruction.asSwitch(); } if (theSwitch.isIntSwitch()) { - rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); + anySimplifications |= + rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); } } } @@ -621,12 +670,13 @@ private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnal Set affectedValues = needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of(); - if (!affectedValues.isEmpty()) { + boolean anyAffectedValues = !affectedValues.isEmpty(); + if (anyAffectedValues) { new TypeAnalysis(appView).narrowing(affectedValues); } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - return !affectedValues.isEmpty(); + return create(anyAffectedValues, anySimplifications); } public void rewriteSingleKeySwitchToIf( @@ -652,19 +702,19 @@ public void rewriteSingleKeySwitchToIf( iterator.replaceCurrentInstruction(replacement); } - private void rewriteIntSwitch( + private boolean rewriteIntSwitch( IRCode code, ListIterator blockIterator, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) { if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) { - return; + return false; } if (theSwitch.numberOfKeys() == 1) { rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch); - return; + return true; } // If there are more than 1 key, we use the following algorithm to find keys to combine. @@ -753,7 +803,9 @@ private void rewriteIntSwitch( if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) { convertSwitchToSwitchAndIfs( code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers); + return true; } + return false; } // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java new file mode 100644 index 0000000000..70402ccd47 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java @@ -0,0 +1,278 @@ +// Copyright (c) 2023, 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.ir.conversion.passes; + +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.ConstNumber; +import com.android.tools.r8.ir.code.DominatorTree; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.If; +import com.android.tools.r8.ir.code.IfType; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; +import com.android.tools.r8.utils.LazyBox; +import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +/** + * This optimization exploits that we can sometimes learn the constant value of an SSA value that + * flows into an if-eq of if-neq instruction. + * + *

Consider the following example: + * + *

+ * 1. if (obj != null) {
+ * 2.  return doStuff();
+ * 3. }
+ * 4. return null;
+ * 
+ * + *

Since we know that `obj` is null in all blocks that are dominated by the false-target of the + * if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and thereby + * save a const-number instruction. + */ +public class RedundantConstNumberRemover extends CodeRewriterPass { + + public RedundantConstNumberRemover(AppView appView) { + super(appView); + } + + @Override + protected String getTimingId() { + return "RedundantConstNumberRemover"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return options.enableRedundantConstNumberOptimization && code.metadata().mayHaveConstNumber(); + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + redundantConstNumberRemoval(code); + return CodeRewriterResult.NONE; + } + + public void redundantConstNumberRemoval(IRCode code) { + if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() + && !appView.options().testing.forceRedundantConstNumberRemoval) { + // See also b/124152497. + return; + } + + LazyBox>> constantsByValue = + new LazyBox<>(() -> getConstantsByValue(code)); + LazyBox dominatorTree = new LazyBox<>(() -> new DominatorTree(code)); + + boolean changed = false; + for (BasicBlock block : code.blocks) { + Instruction lastInstruction = block.getInstructions().getLast(); + if (!lastInstruction.isIf()) { + continue; + } + + If ifInstruction = lastInstruction.asIf(); + IfType type = ifInstruction.getType(); + + Value lhs = ifInstruction.inValues().get(0); + Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null; + + if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) { + // We can only conclude anything from an if-instruction if it is a zero-test or if one of + // the two operands is a constant. + continue; + } + + // If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values + // of the if-instruction from the outcome of the if-instruction. + if (type != IfType.EQ && type != IfType.NE) { + continue; + } + + BasicBlock trueTarget, falseTarget; + if (type == IfType.EQ) { + trueTarget = ifInstruction.getTrueTarget(); + falseTarget = ifInstruction.fallthroughBlock(); + } else { + falseTarget = ifInstruction.getTrueTarget(); + trueTarget = ifInstruction.fallthroughBlock(); + } + + if (ifInstruction.isZeroTest()) { + changed |= + replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree); + if (lhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + 1, lhs, falseTarget, constantsByValue, code, dominatorTree); + } + } else { + assert rhs != null; + if (lhs.isConstNumber()) { + ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber(); + changed |= + replaceDominatedConstNumbers( + lhsAsNumber.getRawValue(), + rhs, + trueTarget, + constantsByValue, + code, + dominatorTree); + if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + negateBoolean(lhsAsNumber), + rhs, + falseTarget, + constantsByValue, + code, + dominatorTree); + } + } else { + assert rhs.isConstNumber(); + ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber(); + changed |= + replaceDominatedConstNumbers( + rhsAsNumber.getRawValue(), + lhs, + trueTarget, + constantsByValue, + code, + dominatorTree); + if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + negateBoolean(rhsAsNumber), + lhs, + falseTarget, + constantsByValue, + code, + dominatorTree); + } + } + } + + if (constantsByValue.computeIfAbsent().isEmpty()) { + break; + } + } + + if (changed) { + code.removeAllDeadAndTrivialPhis(); + } + assert code.isConsistentSSA(appView); + } + + private static Long2ReferenceMap> getConstantsByValue(IRCode code) { + // A map from the raw value of constants in `code` to the const number instructions that define + // the given raw value (irrespective of the type of the raw value). + Long2ReferenceMap> constantsByValue = new Long2ReferenceOpenHashMap<>(); + + // Initialize `constantsByValue`. + for (Instruction instruction : code.instructions()) { + if (instruction.isConstNumber()) { + ConstNumber constNumber = instruction.asConstNumber(); + if (constNumber.outValue().hasLocalInfo()) { + // Not necessarily constant, because it could be changed in the debugger. + continue; + } + long rawValue = constNumber.getRawValue(); + if (constantsByValue.containsKey(rawValue)) { + constantsByValue.get(rawValue).add(constNumber); + } else { + List list = new ArrayList<>(); + list.add(constNumber); + constantsByValue.put(rawValue, list); + } + } + } + return constantsByValue; + } + + private static int negateBoolean(ConstNumber number) { + assert number.outValue().knownToBeBoolean(); + return number.getRawValue() == 0 ? 1 : 0; + } + + private boolean replaceDominatedConstNumbers( + long withValue, + Value newValue, + BasicBlock dominator, + LazyBox>> constantsByValueSupplier, + IRCode code, + LazyBox dominatorTree) { + if (newValue.hasLocalInfo()) { + // We cannot replace a constant with a value that has local info, because that could change + // debugging behavior. + return false; + } + + Long2ReferenceMap> constantsByValue = + constantsByValueSupplier.computeIfAbsent(); + List constantsWithValue = constantsByValue.get(withValue); + if (constantsWithValue == null || constantsWithValue.isEmpty()) { + return false; + } + + boolean changed = false; + + ListIterator constantWithValueIterator = constantsWithValue.listIterator(); + while (constantWithValueIterator.hasNext()) { + ConstNumber constNumber = constantWithValueIterator.next(); + Value value = constNumber.outValue(); + assert !value.hasLocalInfo(); + assert constNumber.getRawValue() == withValue; + + BasicBlock block = constNumber.getBlock(); + + // If the following condition does not hold, then the if-instruction does not dominate the + // block containing the constant, although the true or false target does. + if (block == dominator && block.getPredecessors().size() != 1) { + // This should generally not happen, but it is possible to write bytecode where it does. + assert false; + continue; + } + + if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) { + // We cannot replace a boolean by a none-boolean since that can lead to verification + // errors. For example, the following code fails with "register v1 has type Imprecise + // Constant: 127 but expected Boolean return-1nr". + // + // public boolean convertIntToBoolean(int v1) { + // const/4 v0, 0x1 + // if-eq v1, v0, :eq_true + // const/4 v1, 0x0 + // :eq_true + // return v1 + // } + continue; + } + + if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) { + if (newValue.getType().lessThanOrEqual(value.getType(), appView)) { + value.replaceUsers(newValue); + block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead(); + constantWithValueIterator.remove(); + changed = true; + } else if (value.getType().isNullType()) { + // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of + // the use sites of `value` without introducing a type error. + } + } + } + + if (constantsWithValue.isEmpty()) { + constantsByValue.remove(withValue); + } + + return changed; + } +} diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java index cbda6309d0..4a1a1d4e7b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java @@ -45,7 +45,7 @@ private boolean allSwitchCasesMarkedForRemoval() { && switchCasesToBeRemoved.size() == theSwitch.numberOfKeys(); } - private boolean canBeOptimized() { + boolean canBeOptimized() { assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty(); return switchCasesToBeRemoved != null || hasAlwaysHitCase() || !isFallthroughLive(); } @@ -96,7 +96,7 @@ void markSwitchFallthroughAsNeverHit() { liveFallthrough = false; } - boolean optimize() { + void optimize() { if (canBeOptimized()) { int originalNumberOfSuccessors = block.getSuccessors().size(); IntList removedSuccessorIndices = unlinkDeadSuccessors(); @@ -107,9 +107,7 @@ boolean optimize() { // Replace switch by a new switch where the dead switch cases have been removed. replaceSwitchByOptimizedSwitch(originalNumberOfSuccessors, removedSuccessorIndices); } - return true; } - return false; } private IntList unlinkDeadSuccessors() { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java index f34a6bd20e..85cd30e1f2 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java @@ -5,10 +5,10 @@ package com.android.tools.r8.ir.conversion.passes; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; @@ -32,6 +32,7 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -40,18 +41,42 @@ import java.util.ListIterator; import java.util.Set; -public class ThrowCatchOptimizer { +public class ThrowCatchOptimizer extends CodeRewriterPass { - private final AppView appView; - private final DexItemFactory dexItemFactory; + private final boolean rewriteThrowNull; + + public ThrowCatchOptimizer(AppView appView, boolean isDebug) { + super(appView); + this.rewriteThrowNull = !isDebug; + } public ThrowCatchOptimizer(AppView appView) { - this.appView = appView; - this.dexItemFactory = appView.dexItemFactory(); + super(appView); + this.rewriteThrowNull = false; + } + + @Override + protected String getTimingId() { + return "ThrowCatchOptimizer"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return true; + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + optimizeAlwaysThrowingInstructions(code); + if (rewriteThrowNull) { + rewriteThrowNullPointerException(code); + } + return CodeRewriterResult.NONE; } // Rewrite 'throw new NullPointerException()' to 'throw null'. - public void rewriteThrowNullPointerException(IRCode code) { + private void rewriteThrowNullPointerException(IRCode code) { + boolean hasChanged = false; boolean shouldRemoveUnreachableBlocks = false; for (BasicBlock block : code.blocks) { InstructionListIterator it = block.listIterator(code); @@ -64,7 +89,7 @@ public void rewriteThrowNullPointerException(IRCode code) { if (appView .dexItemFactory() .objectsMethods - .isRequireNonNullMethod(code.method().getReference())) { + .isRequireNonNullMethod(code.context().getReference())) { continue; } @@ -126,6 +151,7 @@ public void rewriteThrowNullPointerException(IRCode code) { valueIsNullTarget, throwInstruction.getPosition()); shouldRemoveUnreachableBlocks = true; + hasChanged = true; } // Check for 'new-instance NullPointerException' with 2 users, not declaring a local and @@ -172,6 +198,7 @@ public void rewriteThrowNullPointerException(IRCode code) { // Replace them with 'const 0' and 'throw'. it.add(nullPointer); it.add(throwInstruction); + hasChanged = true; } } } @@ -186,13 +213,16 @@ public void rewriteThrowNullPointerException(IRCode code) { new TypeAnalysis(appView).narrowing(affectedValues); } } + if (hasChanged) { + code.removeRedundantBlocks(); + } assert code.isConsistentSSA(appView); } // Find all instructions that always throw, split the block after each such instruction and follow // it with a block throwing a null value (which should result in NPE). Note that this throw is not // expected to be ever reached, but is intended to satisfy verifier. - public void optimizeAlwaysThrowingInstructions(IRCode code) { + private void optimizeAlwaysThrowingInstructions(IRCode code) { Set affectedValues = Sets.newIdentityHashSet(); Set blocksToRemove = Sets.newIdentityHashSet(); ListIterator blockIterator = code.listIterator(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java index 68d5c0d2ff..1af288a003 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java @@ -5,6 +5,7 @@ package com.android.tools.r8.ir.conversion.passes.result; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult; public interface CodeRewriterResult { @@ -37,4 +38,8 @@ public boolean hasChanged() { } boolean hasChanged(); + + default ControlFlowSimplificationResult asControlFlowSimplificationResult() { + throw new Unreachable("Not a control flow simplification result."); + } } 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 e6968798e7..19f053fa46 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 @@ -21,11 +21,9 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; import com.android.tools.r8.ir.code.DebugLocalWrite; import com.android.tools.r8.ir.code.DebugLocalsChange; -import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; @@ -40,7 +38,6 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; -import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.common.collect.Streams; @@ -51,11 +48,7 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; -import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; -import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -import java.util.ArrayList; import java.util.List; -import java.util.ListIterator; import java.util.Set; public class CodeRewriter { @@ -209,242 +202,6 @@ public void simplifyDebugLocals(IRCode code) { } } - /** - * This optimization exploits that we can sometimes learn the constant value of an SSA value that - * flows into an if-eq of if-neq instruction. - * - *

Consider the following example: - * - *

-   * 1. if (obj != null) {
-   * 2.  return doStuff();
-   * 3. }
-   * 4. return null;
-   * 
- * - *

Since we know that `obj` is null in all blocks that are dominated by the false-target of the - * if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and - * thereby save a const-number instruction. - */ - public void redundantConstNumberRemoval(IRCode code) { - if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() - && !appView.options().testing.forceRedundantConstNumberRemoval) { - // See also b/124152497. - return; - } - - if (!code.metadata().mayHaveConstNumber()) { - return; - } - - LazyBox>> constantsByValue = - new LazyBox<>(() -> getConstantsByValue(code)); - LazyBox dominatorTree = new LazyBox<>(() -> new DominatorTree(code)); - - boolean changed = false; - for (BasicBlock block : code.blocks) { - Instruction lastInstruction = block.getInstructions().getLast(); - if (!lastInstruction.isIf()) { - continue; - } - - If ifInstruction = lastInstruction.asIf(); - IfType type = ifInstruction.getType(); - - Value lhs = ifInstruction.inValues().get(0); - Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null; - - if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) { - // We can only conclude anything from an if-instruction if it is a zero-test or if one of - // the two operands is a constant. - continue; - } - - // If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values - // of the if-instruction from the outcome of the if-instruction. - if (type != IfType.EQ && type != IfType.NE) { - continue; - } - - BasicBlock trueTarget, falseTarget; - if (type == IfType.EQ) { - trueTarget = ifInstruction.getTrueTarget(); - falseTarget = ifInstruction.fallthroughBlock(); - } else { - falseTarget = ifInstruction.getTrueTarget(); - trueTarget = ifInstruction.fallthroughBlock(); - } - - if (ifInstruction.isZeroTest()) { - changed |= - replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree); - if (lhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - 1, lhs, falseTarget, constantsByValue, code, dominatorTree); - } - } else { - assert rhs != null; - if (lhs.isConstNumber()) { - ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber(); - changed |= - replaceDominatedConstNumbers( - lhsAsNumber.getRawValue(), - rhs, - trueTarget, - constantsByValue, - code, - dominatorTree); - if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - negateBoolean(lhsAsNumber), - rhs, - falseTarget, - constantsByValue, - code, - dominatorTree); - } - } else { - assert rhs.isConstNumber(); - ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber(); - changed |= - replaceDominatedConstNumbers( - rhsAsNumber.getRawValue(), - lhs, - trueTarget, - constantsByValue, - code, - dominatorTree); - if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - negateBoolean(rhsAsNumber), - lhs, - falseTarget, - constantsByValue, - code, - dominatorTree); - } - } - } - - if (constantsByValue.computeIfAbsent().isEmpty()) { - break; - } - } - - if (changed) { - code.removeAllDeadAndTrivialPhis(); - } - assert code.isConsistentSSA(appView); - } - - private static Long2ReferenceMap> getConstantsByValue(IRCode code) { - // A map from the raw value of constants in `code` to the const number instructions that define - // the given raw value (irrespective of the type of the raw value). - Long2ReferenceMap> constantsByValue = new Long2ReferenceOpenHashMap<>(); - - // Initialize `constantsByValue`. - for (Instruction instruction : code.instructions()) { - if (instruction.isConstNumber()) { - ConstNumber constNumber = instruction.asConstNumber(); - if (constNumber.outValue().hasLocalInfo()) { - // Not necessarily constant, because it could be changed in the debugger. - continue; - } - long rawValue = constNumber.getRawValue(); - if (constantsByValue.containsKey(rawValue)) { - constantsByValue.get(rawValue).add(constNumber); - } else { - List list = new ArrayList<>(); - list.add(constNumber); - constantsByValue.put(rawValue, list); - } - } - } - return constantsByValue; - } - - private static int negateBoolean(ConstNumber number) { - assert number.outValue().knownToBeBoolean(); - return number.getRawValue() == 0 ? 1 : 0; - } - - private boolean replaceDominatedConstNumbers( - long withValue, - Value newValue, - BasicBlock dominator, - LazyBox>> constantsByValueSupplier, - IRCode code, - LazyBox dominatorTree) { - if (newValue.hasLocalInfo()) { - // We cannot replace a constant with a value that has local info, because that could change - // debugging behavior. - return false; - } - - Long2ReferenceMap> constantsByValue = - constantsByValueSupplier.computeIfAbsent(); - List constantsWithValue = constantsByValue.get(withValue); - if (constantsWithValue == null || constantsWithValue.isEmpty()) { - return false; - } - - boolean changed = false; - - ListIterator constantWithValueIterator = constantsWithValue.listIterator(); - while (constantWithValueIterator.hasNext()) { - ConstNumber constNumber = constantWithValueIterator.next(); - Value value = constNumber.outValue(); - assert !value.hasLocalInfo(); - assert constNumber.getRawValue() == withValue; - - BasicBlock block = constNumber.getBlock(); - - // If the following condition does not hold, then the if-instruction does not dominate the - // block containing the constant, although the true or false target does. - if (block == dominator && block.getPredecessors().size() != 1) { - // This should generally not happen, but it is possible to write bytecode where it does. - assert false; - continue; - } - - if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) { - // We cannot replace a boolean by a none-boolean since that can lead to verification - // errors. For example, the following code fails with "register v1 has type Imprecise - // Constant: 127 but expected Boolean return-1nr". - // - // public boolean convertIntToBoolean(int v1) { - // const/4 v0, 0x1 - // if-eq v1, v0, :eq_true - // const/4 v1, 0x0 - // :eq_true - // return v1 - // } - continue; - } - - if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) { - if (newValue.getType().lessThanOrEqual(value.getType(), appView)) { - value.replaceUsers(newValue); - block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead(); - constantWithValueIterator.remove(); - changed = true; - } else if (value.getType().isNullType()) { - // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of - // the use sites of `value` without introducing a type error. - } - } - } - - if (constantsWithValue.isEmpty()) { - constantsByValue.remove(withValue); - } - - return changed; - } - /** * Remove moves that are not actually used by instructions in exiting paths. These moves can arise * due to debug local info needing a particular value and the live-interval for it then moves it diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index e9b180c578..94fabb76be 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java @@ -60,11 +60,15 @@ public void run(IRCode code, Timing timing) { removeDeadInstructions(worklist, code, block, valueIsDeadAnalysis); removeDeadPhis(worklist, block, valueIsDeadAnalysis); } - } while (branchSimplifier.simplifyIf(code).anySimplifications() + } while (branchSimplifier + .simplifyIf(code) + .asControlFlowSimplificationResult() + .anySimplifications() || removeUnneededCatchHandlers(code)); code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + assert verifyNoDeadCode(code); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index d3d2a08bff..b46d0a5bfa 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -252,7 +252,7 @@ public final void processMethodCode( new TrivialCheckCastAndInstanceOfRemover(appView) .run(code, methodProcessor, methodProcessingContext, Timing.empty()); // If a method was inlined we may be able to prune additional branches. - new BranchSimplifier(appView).simplifyBranches(code); + new BranchSimplifier(appView).run(code, Timing.empty()); // If a method was inlined we may see more trivial computation/conversion of String. boolean isDebugMode = appView.options().debug || method.getOrComputeReachabilitySensitive(appView); diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java index 1c0d3de9d7..f7cf0b5a21 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java @@ -270,7 +270,7 @@ private void rewriteMethod( rewriter.rewriteCode(ir, initializedClassesWithContexts, prunedFields); // Run dead code elimination. - new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(ir); + new ThrowCatchOptimizer(appView).run(ir, Timing.empty()); rewriter.getDeadCodeRemover().run(ir, Timing.empty()); // Finalize to class files or dex. From 0c1f3f90ac3e188a9018ae308a56d2bc5a54aee7 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Wed, 14 Jun 2023 13:14:06 +0200 Subject: [PATCH 141/153] Reproduce issue with null argument and enum unboxer Bug: b/287193321 Change-Id: I812dfaa3139942ae1cf13dadc81c68b31ceb3aeb --- .../EnumUnboxNullArgumentTest.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNullArgumentTest.java diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNullArgumentTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNullArgumentTest.java new file mode 100644 index 0000000000..7c5dfb8826 --- /dev/null +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNullArgumentTest.java @@ -0,0 +1,96 @@ +// Copyright (c) 2023, 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.enumunboxing; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThrows; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** This is a regression test for b/287193321. */ +@RunWith(Parameterized.class) +public class EnumUnboxNullArgumentTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testR8() { + // TODO(b/287193321): We should not fail compilation. + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .compileWithExpectedDiagnostics( + diagnostics -> { + if (parameters.isDexRuntime()) { + diagnostics.assertErrorMessageThatMatches( + containsString("Cannot constrain type")); + } + })); + } + + public enum MyEnum { + FOO("1"), + BAR("2"); + + final String value; + + MyEnum(String value) { + this.value = value; + } + } + + public static class Main { + + public static void main(String[] args) { + // Delay observing that arguments to bar is null until we've inlined foo() and getEnum(). + String foo = foo(); + String[] bar = bar(getEnum(), foo); + // To ensure bar(MyEnum,String) is not inlined in the first round we add a few additional + // calls that will be stripped during IR-processing of main. + if (foo != null) { + bar(MyEnum.FOO, foo); + bar(MyEnum.BAR, foo); + } + for (String b : bar) { + System.out.println(b); + } + } + + public static String[] bar(MyEnum myEnum, String foo) { + if (System.currentTimeMillis() > 1) { + // Ensure that the construction is in a separate block than entry() to have constant + // canonicalization align the two null values into one. + return new String[] {myEnum.value, foo}; + } + return new String[] {}; + } + + public static MyEnum getEnum() { + return null; + } + + public static String foo() { + return null; + } + } +} From 25ceda9b2be338745a160ff5ad961240b56fd7dd Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Tue, 20 Jun 2023 10:24:42 +0200 Subject: [PATCH 142/153] Maintain LIR during the full R8 optimize pipeline Bug: b/225838009 Change-Id: I672b778d025b754504894305d9d5178e30b77fe5 --- src/main/java/com/android/tools/r8/R8.java | 1 + .../com/android/tools/r8/graph/CfCode.java | 2 +- .../java/com/android/tools/r8/graph/Code.java | 2 +- .../com/android/tools/r8/graph/DexCode.java | 2 +- .../android/tools/r8/graph/UseRegistry.java | 11 ++ .../BytecodeMetadataProvider.java | 4 + .../fieldaccess/FieldAccessAnalysis.java | 38 +---- .../TrivialFieldAccessReprocessor.java | 24 ++- .../android/tools/r8/ir/code/Position.java | 9 ++ .../tools/r8/ir/conversion/IRConverter.java | 27 +++- .../ir/conversion/PrimaryR8IRConverter.java | 2 - .../tools/r8/lightir/IR2LirConverter.java | 20 ++- .../tools/r8/lightir/Lir2IRConverter.java | 140 ++++++++++-------- .../android/tools/r8/lightir/LirBuilder.java | 64 +++++++- .../com/android/tools/r8/lightir/LirCode.java | 101 +++++++++---- .../tools/r8/lightir/LirDecodingStrategy.java | 27 +++- .../android/tools/r8/lightir/LirStrategy.java | 6 + .../r8/lightir/LirUseRegistryCallback.java | 10 +- .../r8/lightir/PhiInInstructionsStrategy.java | 6 + .../tools/r8/utils/InternalOptions.java | 6 +- .../tools/r8/ir/optimize/R8InliningTest.java | 3 +- .../r8/kotlin/R8KotlinDataClassTest.java | 13 +- 22 files changed, 357 insertions(+), 161 deletions(-) diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 0ce7a4e9da..78cd2c8525 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -975,6 +975,7 @@ private static boolean verifyMovedMethodsHaveOriginalMethodPosition( private static boolean verifyOriginalMethodInPosition( Code code, DexMethod originalMethod, ProgramMethod context) { code.forEachPosition( + originalMethod, position -> { if (position.isOutlineCaller()) { // Check the outlined positions for the original method diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java index 165878d427..b7feac3ffd 100644 --- a/src/main/java/com/android/tools/r8/graph/CfCode.java +++ b/src/main/java/com/android/tools/r8/graph/CfCode.java @@ -966,7 +966,7 @@ public void acceptError(CfCodeDiagnostics diagnostics) { } @Override - public void forEachPosition(Consumer positionConsumer) { + public void forEachPosition(DexMethod method, Consumer positionConsumer) { for (CfInstruction instruction : getInstructions()) { if (instruction.isPosition()) { positionConsumer.accept(instruction.asPosition().getPosition()); diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java index 3332852d35..193e758d24 100644 --- a/src/main/java/com/android/tools/r8/graph/Code.java +++ b/src/main/java/com/android/tools/r8/graph/Code.java @@ -235,7 +235,7 @@ private static Position removeSameMethodAndLineZero( return calleePosition.withOutermostCallerPosition(callerPosition); } - public void forEachPosition(Consumer positionConsumer) { + public void forEachPosition(DexMethod method, Consumer positionConsumer) { // Intentionally empty. Override where we have fully build CF or DEX code. } } diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java index 5932bb58a1..325eb7970d 100644 --- a/src/main/java/com/android/tools/r8/graph/DexCode.java +++ b/src/main/java/com/android/tools/r8/graph/DexCode.java @@ -866,7 +866,7 @@ public void writeDex( } @Override - public void forEachPosition(Consumer positionConsumer) { + public void forEachPosition(DexMethod method, Consumer positionConsumer) { if (getDebugInfo() == null || getDebugInfo().isPcBasedInfo()) { return; } diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java index 3797119a3b..02078ee76a 100644 --- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java +++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java @@ -6,6 +6,7 @@ import com.android.tools.r8.dex.code.CfOrDexInstanceFieldRead; import com.android.tools.r8.dex.code.CfOrDexInstruction; import com.android.tools.r8.dex.code.CfOrDexStaticFieldRead; +import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.ir.code.InvokeType; import com.android.tools.r8.utils.TraversalContinuation; @@ -92,6 +93,11 @@ public void registerInvokeSpecial(DexMethod method) { public abstract void registerInstanceFieldRead(DexField field); + public void registerInstanceFieldReadWithMetadata( + DexField field, BytecodeInstructionMetadata metadata) { + registerInstanceFieldRead(field); + } + public void registerInstanceFieldReadInstruction(CfOrDexInstanceFieldRead instruction) { registerInstanceFieldRead(instruction.getField()); } @@ -120,6 +126,11 @@ public void registerNewUnboxedEnumInstance(DexType type) { public abstract void registerStaticFieldRead(DexField field); + public void registerStaticFieldReadWithMetadata( + DexField field, BytecodeInstructionMetadata metadata) { + registerStaticFieldRead(field); + } + public void registerStaticFieldReadInstruction(CfOrDexStaticFieldRead instruction) { registerStaticFieldRead(instruction.getField()); } diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java index eab9e7bd27..ff0837b631 100644 --- a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java +++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java @@ -35,6 +35,10 @@ public static BytecodeMetadataProvider empty() { return EMPTY; } + public int size() { + return backing.size(); + } + /** * Returns the metadata for a given IR instruction that should be attached to the CF or DEX * instruction when finalizing the IR to CF or DEX. diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java index ca741c9f7b..d871616373 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java @@ -36,14 +36,8 @@ public FieldAccessAnalysis(AppView appView) { this.fieldBitAccessAnalysis = options.enableFieldBitAccessAnalysis ? new FieldBitAccessAnalysis() : null; this.fieldAssignmentTracker = new FieldAssignmentTracker(appView); - if (options.testing.canUseLir(appView)) { - // When using LIR the bytecode metadata is computed later during finalization via IR. - this.fieldReadForInvokeReceiverAnalysis = null; - this.fieldReadForWriteAnalysis = null; - } else { - this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView); - this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); - } + this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView); + this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); } @VisibleForTesting @@ -117,32 +111,4 @@ public void recordFieldAccesses( } } } - - public static BytecodeMetadataProvider computeBytecodeMetadata( - IRCode irCode, AppView appView) { - // This rebuilding of metadata should only be used in the LIR pipeline where the info is - // discarded when translating from IR to LIR. - assert appView.options().testing.canUseLir(appView); - BytecodeMetadataProvider bytecodeMetadataProvider = BytecodeMetadataProvider.empty(); - if (irCode.metadata().mayHaveFieldInstruction()) { - BytecodeMetadataProvider.Builder builder = BytecodeMetadataProvider.builder(); - FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis = - new FieldReadForInvokeReceiverAnalysis(appView); - FieldReadForWriteAnalysis fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); - for (Instruction instruction : irCode.instructions()) { - if (instruction.isFieldInstruction()) { - FieldInstruction fieldInstruction = instruction.asFieldInstruction(); - ProgramField field = - appView.appInfo().resolveField(fieldInstruction.getField()).getProgramField(); - if (field != null) { - fieldReadForInvokeReceiverAnalysis.recordFieldAccess( - fieldInstruction, field, builder, irCode.context()); - fieldReadForWriteAnalysis.recordFieldAccess(fieldInstruction, field, builder); - } - } - } - bytecodeMetadataProvider = builder.build(); - } - return bytecodeMetadataProvider; - } } diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java index 4525030ef2..cb7bbbb8bc 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java @@ -441,38 +441,50 @@ private void destroyFieldAccessContexts(DexEncodedField field) { } } + @Override + public void registerInstanceFieldReadWithMetadata( + DexField field, BytecodeInstructionMetadata metadata) { + registerFieldAccess(field, false, false, metadata); + } + @Override public void registerInstanceFieldRead(DexField field) { - registerFieldAccess(field, false, false, BytecodeInstructionMetadata.none()); + registerInstanceFieldReadWithMetadata(field, BytecodeInstructionMetadata.none()); } @Override public void registerInstanceFieldReadInstruction(CfOrDexInstanceFieldRead instruction) { BytecodeInstructionMetadata metadata = getContext().getDefinition().getCode().getMetadata(instruction); - registerFieldAccess(instruction.getField(), false, false, metadata); + registerInstanceFieldReadWithMetadata(instruction.getField(), metadata); } @Override public void registerInstanceFieldWrite(DexField field) { - registerFieldAccess(field, false, true, null); + registerFieldAccess(field, false, true, BytecodeInstructionMetadata.none()); + } + + @Override + public void registerStaticFieldReadWithMetadata( + DexField field, BytecodeInstructionMetadata metadata) { + registerFieldAccess(field, true, false, metadata); } @Override public void registerStaticFieldRead(DexField field) { - registerFieldAccess(field, true, false, BytecodeInstructionMetadata.none()); + registerStaticFieldReadWithMetadata(field, BytecodeInstructionMetadata.none()); } @Override public void registerStaticFieldReadInstruction(CfOrDexStaticFieldRead instruction) { BytecodeInstructionMetadata metadata = getContext().getDefinition().getCode().getMetadata(instruction); - registerFieldAccess(instruction.getField(), true, false, metadata); + registerStaticFieldReadWithMetadata(instruction.getField(), metadata); } @Override public void registerStaticFieldWrite(DexField field) { - registerFieldAccess(field, true, true, null); + registerFieldAccess(field, true, true, BytecodeInstructionMetadata.none()); } @Override diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java index 4c809dc3ee..b27f4474ab 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Position.java +++ b/src/main/java/com/android/tools/r8/ir/code/Position.java @@ -48,6 +48,10 @@ private Position( this.isD8R8Synthesized = isD8R8Synthesized; } + public boolean isSourcePosition() { + return false; + } + public boolean isSyntheticPosition() { return false; } @@ -351,6 +355,11 @@ private SourcePosition( assert callerPosition == null || callerPosition.method != null; } + @Override + public boolean isSourcePosition() { + return true; + } + @Override public boolean hasFile() { return file != null; diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 994d45bea8..44656cf1f3 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -16,6 +16,7 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider; +import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; import com.android.tools.r8.ir.analysis.TypeChecker; import com.android.tools.r8.ir.analysis.VerifyTypesHelper; import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation; @@ -26,6 +27,7 @@ import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InstructionIterator; +import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.passes.ArrayConstructionSimplifier; import com.android.tools.r8.ir.conversion.passes.BinopRewriter; @@ -566,6 +568,7 @@ Timing optimize( timing.begin("Lens rewrite"); lensCodeRewriter.rewrite(code, context, methodProcessor); timing.end(); + previous = printMethod(code, "IR after disable assertions (SSA)", previous); } assert !method.isProcessed() || !isDebugMode @@ -1098,7 +1101,11 @@ private > IRCode doRoundtripWithStrategy( IRCode code, S strategy, String name, Timing timing) { timing.begin("IR->LIR (" + name + ")"); LirCode lirCode = - IR2LirConverter.translate(code, strategy.getEncodingStrategy(), appView.options()); + IR2LirConverter.translate( + code, + BytecodeMetadataProvider.empty(), + strategy.getEncodingStrategy(), + appView.options()); timing.end(); // Check that printing does not fail. String lirString = lirCode.toString(); @@ -1106,7 +1113,13 @@ private > IRCode doRoundtripWithStrategy( timing.begin("LIR->IR (" + name + ")"); IRCode irCode = Lir2IRConverter.translate( - code.context(), lirCode, strategy.getDecodingStrategy(lirCode, null), appView); + code.context(), + lirCode, + strategy.getDecodingStrategy(lirCode, new NumberGenerator()), + appView, + null, + RewrittenPrototypeDescription.none(), + appView.graphLens().getOriginalMethodSignature(code.context().getReference())); timing.end(); return irCode; } @@ -1117,10 +1130,12 @@ private void finalizeToLir( BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing) { assert deadCodeRemover.verifyNoDeadCode(code); - assert BytecodeMetadataProvider.empty() == bytecodeMetadataProvider; LirCode lirCode = IR2LirConverter.translate( - code, LirStrategy.getDefaultStrategy().getEncodingStrategy(), appView.options()); + code, + bytecodeMetadataProvider, + LirStrategy.getDefaultStrategy().getEncodingStrategy(), + appView.options()); ProgramMethod method = code.context(); method.setCode(lirCode, appView); markProcessed(code, feedback); @@ -1271,8 +1286,8 @@ public void finalizeLirMethodToOutputFormat(ProgramMethod method) { } Timing onThreadTiming = Timing.empty(); IRCode irCode = method.buildIR(appView); - BytecodeMetadataProvider bytecodeMetadataProvider = - FieldAccessAnalysis.computeBytecodeMetadata(irCode, appView.withLiveness()); + // Processing is done and no further uses of the meta-data should arise. + BytecodeMetadataProvider bytecodeMetadataProvider = BytecodeMetadataProvider.empty(); // During processing optimization info may cause previously live code to become dead. // E.g., we may now have knowledge that an invoke does not have side effects. // Thus, we re-run the dead-code remover now as it is assumed complete by CF/DEX finalization. diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java index 1187f29fa9..9df87ae5a5 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java @@ -101,7 +101,6 @@ private DexApplication internalOptimize( lastWaveDone(postMethodProcessorBuilder, executorService); eventConsumer.finished(appView); assert appView.graphLens() == graphLensForPrimaryOptimizationPass; - finalizeLirToOutputFormat(timing, executorService); timing.end(); } @@ -178,7 +177,6 @@ private DexApplication internalOptimize( eventConsumer.finished(appView); assert appView.graphLens() == graphLensForSecondaryOptimizationPass; } - finalizeLirToOutputFormat(timing, executorService); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java index 3bc2fe67a7..7f3335f94b 100644 --- a/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java +++ b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.lightir; +import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.CatchHandlers; @@ -26,20 +27,30 @@ public class IR2LirConverter { private final IRCode irCode; private final LirEncodingStrategy strategy; + BytecodeMetadataProvider bytecodeMetadataProvider; private final LirBuilder builder; private IR2LirConverter( - InternalOptions options, IRCode irCode, LirEncodingStrategy strategy) { + InternalOptions options, + IRCode irCode, + LirEncodingStrategy strategy, + BytecodeMetadataProvider bytecodeMetadataProvider) { this.irCode = irCode; this.strategy = strategy; + this.bytecodeMetadataProvider = bytecodeMetadataProvider; this.builder = new LirBuilder<>(irCode.context().getReference(), strategy, options) - .setMetadata(irCode.metadata()); + .setMetadata(irCode.metadata()) + .prepareForBytecodeInstructionMetadata(bytecodeMetadataProvider.size()); } public static LirCode translate( - IRCode irCode, LirEncodingStrategy strategy, InternalOptions options) { - return new IR2LirConverter<>(options, irCode, strategy).internalTranslate(); + IRCode irCode, + BytecodeMetadataProvider bytecodeMetadataProvider, + LirEncodingStrategy strategy, + InternalOptions options) { + return new IR2LirConverter<>(options, irCode, strategy, bytecodeMetadataProvider) + .internalTranslate(); } private void recordBlock(BasicBlock block, int blockIndex) { @@ -89,6 +100,7 @@ private void computeInstructions() { Instruction instruction = it.next(); assert !instruction.hasOutValue() || strategy.verifyValueIndex(instruction.outValue(), currentValueIndex); + builder.setCurrentMetadata(bytecodeMetadataProvider.getMetadata(instruction)); builder.setCurrentPosition(instruction.getPosition()); if (!instruction.getDebugValues().isEmpty()) { builder.setDebugLocalEnds(currentValueIndex, instruction.getDebugValues()); diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java index 4315a85582..f455dda574 100644 --- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java +++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java @@ -22,6 +22,7 @@ import com.android.tools.r8.graph.proto.RewrittenTypeInfo; import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Add; import com.android.tools.r8.ir.code.And; @@ -96,9 +97,12 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.code.Xor; +import com.android.tools.r8.ir.conversion.ExtraParameter; +import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload; import com.android.tools.r8.lightir.LirCode.PositionEntry; +import com.android.tools.r8.lightir.LirCode.StructuredPositionEntry; import com.android.tools.r8.lightir.LirCode.TryCatchTable; import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo; import com.android.tools.r8.utils.ListUtils; @@ -117,28 +121,11 @@ public class Lir2IRConverter { private Lir2IRConverter() {} - public static IRCode translate( - ProgramMethod method, - LirCode lirCode, - LirDecodingStrategy strategy, - AppView appView) { - return translate( - method, - lirCode, - strategy, - appView, - new NumberGenerator(), - null, - RewrittenPrototypeDescription.none(), - appView.graphLens().getOriginalMethodSignature(method.getReference())); - } - public static IRCode translate( ProgramMethod method, LirCode lirCode, LirDecodingStrategy strategy, AppView appView, - NumberGenerator valueNumberGenerator, Position callerPosition, RewrittenPrototypeDescription protoChanges, DexMethod originalMethod) { @@ -149,13 +136,15 @@ public static IRCode translate( method.getDefinition().isD8R8Synthesized(), appView, strategy, - valueNumberGenerator, callerPosition, protoChanges); parser.parseArguments(method); parser.ensureDebugInfo(); lirCode.forEach(view -> view.accept(parser)); - return parser.getIRCode(method); + IRCode irCode = parser.getIRCode(method); + // Some instructions have bottom types (e.g., phis). Compute their actual types by widening. + new TypeAnalysis(appView).widening(irCode); + return irCode; } /** @@ -168,8 +157,8 @@ private static class Parser extends LirParsedInstructionCallback { private final AppView appView; private final LirCode code; + private final DexMethod originalMethod; private final LirDecodingStrategy strategy; - private final NumberGenerator valueNumberGenerator; private final NumberGenerator basicBlockNumberGenerator = new NumberGenerator(); private final RewrittenPrototypeDescription protoChanges; @@ -187,47 +176,46 @@ private static class Parser extends LirParsedInstructionCallback { public Parser( LirCode code, - DexMethod method, + DexMethod originalMethod, boolean isD8R8Synthesized, AppView appView, LirDecodingStrategy strategy, - NumberGenerator valueNumberGenerator, Position callerPosition, RewrittenPrototypeDescription protoChanges) { super(code); this.appView = appView; this.code = code; + this.originalMethod = originalMethod; this.strategy = strategy; - this.valueNumberGenerator = valueNumberGenerator; this.protoChanges = protoChanges; assert protoChanges != null; if (callerPosition == null) { buildForInlining = false; positionTable = code.getPositionTable(); // Recreate the preamble position. This is active for arguments and code with no positions. - currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build(); + currentPosition = SyntheticPosition.builder().setLine(0).setMethod(originalMethod).build(); } else { buildForInlining = true; PositionEntry[] inlineePositions = code.getPositionTable(); Position inlineePreamble = null; - if (inlineePositions.length > 0 && inlineePositions[0].fromInstructionIndex == 0) { - inlineePreamble = inlineePositions[0].position; + if (inlineePositions.length > 0 && inlineePositions[0].getFromInstructionIndex() == 0) { + inlineePreamble = inlineePositions[0].getPosition(originalMethod); } CanonicalPositions canonicalPositions = new CanonicalPositions( callerPosition, inlineePositions.length, - method, + originalMethod, isD8R8Synthesized, inlineePreamble); currentPosition = canonicalPositions.getPreamblePosition(); positionTable = new PositionEntry[inlineePositions.length]; for (int i = 0; i < inlineePositions.length; i++) { PositionEntry inlineeEntry = inlineePositions[i]; - Position inlineePosition = inlineeEntry.position; + Position inlineePosition = inlineeEntry.getPosition(originalMethod); positionTable[i] = - new PositionEntry( - inlineeEntry.fromInstructionIndex, + new StructuredPositionEntry( + inlineeEntry.getFromInstructionIndex(), canonicalPositions.getCanonical( inlineePosition .builderWithCopy() @@ -269,8 +257,8 @@ private void ensureCurrentBlock() { private void ensureCurrentPosition() { if (nextPositionEntry != null - && nextPositionEntry.fromInstructionIndex <= nextInstructionIndex) { - currentPosition = nextPositionEntry.position; + && nextPositionEntry.getFromInstructionIndex() <= nextInstructionIndex) { + currentPosition = nextPositionEntry.getPosition(originalMethod); advanceNextPositionEntry(); } } @@ -305,16 +293,27 @@ public void parseArguments(ProgramMethod method) { ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(index); if (argumentInfo.isRemovedArgumentInfo()) { RemovedArgumentInfo removedArgumentInfo = argumentInfo.asRemovedArgumentInfo(); - addArgument(removedArgumentInfo.getType(), index++); + addNonThisArgument(removedArgumentInfo.getType(), index++); numberOfRemovedArguments++; } else if (argumentInfo.isRewrittenTypeInfo()) { RewrittenTypeInfo rewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo(); int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments); assert method.getArgumentType(newArgumentIndex) == rewrittenTypeInfo.getNewType(); - addArgument(rewrittenTypeInfo.getOldType(), index++); + addNonThisArgument(rewrittenTypeInfo.getOldType(), index++); } else { int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments); - addArgument(method.getArgumentType(newArgumentIndex), index++); + addNonThisArgument(method.getArgumentType(newArgumentIndex), index++); + } + } + + for (ExtraParameter extraParameter : protoChanges.getExtraParameters()) { + int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments); + DexType extraArgumentType = method.getArgumentType(newArgumentIndex); + if (extraParameter instanceof ExtraUnusedNullParameter) { + // Note that we do *not* increment the index here as that would shift the SSA value map. + addUnusedArgument(extraArgumentType); + } else { + addNonThisArgument(extraArgumentType, index++); } } @@ -356,21 +355,12 @@ public IRCode getIRCode(ProgramMethod method) { } } } - if (!buildForInlining) { - // The decoding strategy will increment this on demand when built for inlining. - // Not incrementing for normal building results in nice order of instruction index and - // value number. - int lastValueIndex = getCurrentValueIndex(); - for (int i = 0; i < lastValueIndex; ++i) { - valueNumberGenerator.next(); - } - } return new IRCode( appView.options(), method, Position.syntheticNone(), blockList, - valueNumberGenerator, + strategy.getValueNumberGenerator(), basicBlockNumberGenerator, code.getMetadataForIR(), method.getOrigin(), @@ -462,20 +452,39 @@ private void addInstruction(Instruction instruction) { } private void addThisArgument(DexType type) { - Argument argument = addArgument(type, 0); + boolean receiverCouldBeNull = buildForInlining; + Nullability nullability = + receiverCouldBeNull ? Nullability.maybeNull() : Nullability.definitelyNotNull(); + TypeElement typeElement = type.toTypeElement(appView, nullability); + Value dest = + strategy.getValueDefinitionForInstructionIndex(0, typeElement, code::getDebugLocalInfo); + Argument argument = internalAddArgument(dest, false); argument.outValue().markAsThis(); } - private Argument addArgument(DexType type, int index) { - // Arguments are not included in the "instructions" so this does not call "addInstruction" - // which would otherwise advance the state. + private void addNonThisArgument(DexType type, int index) { TypeElement typeElement = type.toTypeElement(appView); Value dest = strategy.getValueDefinitionForInstructionIndex( index, typeElement, code::getDebugLocalInfo); - Argument argument = new Argument(dest, index, type.isBooleanType()); + internalAddArgument(dest, type.isBooleanType()); + } + + private void addUnusedArgument(DexType type) { + // Extra unused null arguments don't have valid indexes in LIR and must not adjust existing + // indexes. + TypeElement typeElement = + type.isReferenceType() ? TypeElement.getNull() : type.toTypeElement(appView); + Value dest = strategy.getFreshUnusedValue(typeElement); + internalAddArgument(dest, false); + } + + private Argument internalAddArgument(Value dest, boolean isBooleanType) { assert currentBlock != null; assert currentPosition.isSyntheticPosition() || buildForInlining; + // Arguments are not included in the "instructions" so this does not call "addInstruction" + // which would otherwise advance the state. + Argument argument = new Argument(dest, currentBlock.size(), isBooleanType); argument.setPosition(currentPosition); currentBlock.getInstructions().add(argument); argument.setBlock(currentBlock); @@ -762,7 +771,8 @@ public void onInvokeInterface(DexMethod target, List arguments) { @Override public void onInvokeCustom(DexCallSite callSite, List arguments) { - Value dest = getInvokeInstructionOutputValue(callSite.methodProto); + // The actual type of invoke custom may have multiple interface types. Defer type to widening. + Value dest = getOutValueForNextInstruction(TypeElement.getBottom()); List ssaArgumentValues = getValues(arguments); InvokeCustom instruction = new InvokeCustom(callSite, dest, ssaArgumentValues); addInstruction(instruction); @@ -819,7 +829,9 @@ public void onInstancePut(DexField field, EV object, EV value) { @Override public void onNewArrayEmpty(DexType type, EV size) { - Value dest = getOutValueForNextInstruction(type.toTypeElement(appView)); + Value dest = + getOutValueForNextInstruction( + type.toTypeElement(appView, Nullability.definitelyNotNull())); addInstruction(new NewArrayEmpty(dest, getValue(size), type)); } @@ -854,13 +866,13 @@ public void onArrayLength(EV arrayValueIndex) { @Override public void onCheckCast(DexType type, EV value, boolean ignoreCompatRules) { - Value dest = getOutValueForNextInstruction(type.toTypeElement(appView)); + Value dest = getOutValueForNextInstruction(type.toTypeElement(appView, Nullability.bottom())); addInstruction(new CheckCast(dest, getValue(value), type, ignoreCompatRules)); } @Override public void onSafeCheckCast(DexType type, EV value) { - Value dest = getOutValueForNextInstruction(type.toTypeElement(appView)); + Value dest = getOutValueForNextInstruction(type.toTypeElement(appView, Nullability.bottom())); addInstruction(new SafeCheckCast(dest, getValue(value), type)); } @@ -877,7 +889,8 @@ public void onDebugPosition() { @Override public void onPhi(DexType type, List operands) { - Phi phi = getPhiForNextInstructionAndAdvanceState(type.toTypeElement(appView)); + // The type of the phi is determined by its operands during type widening. + Phi phi = getPhiForNextInstructionAndAdvanceState(TypeElement.getBottom()); List values = new ArrayList<>(operands.size()); for (int i = 0; i < operands.size(); i++) { values.add(getValue(operands.get(i))); @@ -887,18 +900,17 @@ public void onPhi(DexType type, List operands) { @Override public void onMoveException(DexType exceptionType) { - Value dest = getOutValueForNextInstruction(exceptionType.toTypeElement(appView)); + Value dest = + getOutValueForNextInstruction( + exceptionType.toTypeElement(appView, Nullability.definitelyNotNull())); addInstruction(new MoveException(dest, exceptionType, appView.options())); } @Override public void onDebugLocalWrite(EV srcIndex) { - Value src = getValue(srcIndex); - // The type is in the local table, so initialize it with bottom and reset with the local info. + // The type is dependent on the source so type widening will determine it. Value dest = getOutValueForNextInstruction(TypeElement.getBottom()); - TypeElement type = dest.getLocalInfo().type.toTypeElement(appView); - dest.setType(type); - addInstruction(new DebugLocalWrite(dest, src)); + addInstruction(new DebugLocalWrite(dest, getValue(srcIndex))); } @Override @@ -972,8 +984,10 @@ public void onMonitorExit(EV value) { } @Override - public void onArrayGetObject(DexType type, EV array, EV index) { - Value dest = getOutValueForNextInstruction(type.toTypeElement(appView)); + public void onArrayGetObject(DexType unusedType, EV array, EV index) { + // TODO(b/225838009): Remove type and unify object/primitive methods now that it is computed. + // The output type depends on its input array member type, so it is computed by widening. + Value dest = getOutValueForNextInstruction(TypeElement.getBottom()); addInstruction(new ArrayGet(MemberType.OBJECT, dest, getValue(array), getValue(index))); } diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java index 8c8e4c5e2a..68b5c0e7a6 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java +++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java @@ -20,6 +20,7 @@ import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.CatchHandlers; @@ -34,7 +35,9 @@ import com.android.tools.r8.ir.code.Position.SyntheticPosition; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.lightir.LirCode.DebugLocalInfoTable; +import com.android.tools.r8.lightir.LirCode.LinePositionEntry; import com.android.tools.r8.lightir.LirCode.PositionEntry; +import com.android.tools.r8.lightir.LirCode.StructuredPositionEntry; import com.android.tools.r8.lightir.LirCode.TryCatchTable; import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo; import com.android.tools.r8.utils.InternalOptions; @@ -73,6 +76,9 @@ public class LirBuilder { private final LirEncodingStrategy strategy; + private BytecodeInstructionMetadata currentMetadata; + private Int2ReferenceMap metadataMap; + private Position currentPosition; private Position flushedPosition; @@ -176,6 +182,18 @@ public void addTryCatchHanders(int blockIndex, CatchHandlers handlers) tryCatchRanges.put(blockIndex, handlers); } + public LirBuilder prepareForBytecodeInstructionMetadata(int expectedSize) { + if (expectedSize > 0) { + metadataMap = new Int2ReferenceOpenHashMap<>(expectedSize); + } + return this; + } + + public LirBuilder setCurrentMetadata(BytecodeInstructionMetadata metadata) { + currentMetadata = metadata; + return this; + } + public LirBuilder setCurrentPosition(Position position) { assert position != null; if (!position.isNone()) { @@ -184,11 +202,37 @@ public LirBuilder setCurrentPosition(Position position) { return this; } - private void setPositionIndex(int instructionIndex, Position position) { + private boolean isSimpleLinePosition(Position position) { + return (position.isSourcePosition() || position.isSyntheticPosition()) + && !position.hasCallerPosition(); + } + + private boolean setPositionIndex(int instructionIndex, Position position) { assert positionTable.isEmpty() - || ListUtils.last(positionTable).fromInstructionIndex < instructionIndex; - assert positionTable.isEmpty() || !ListUtils.last(positionTable).position.equals(position); - positionTable.add(new PositionEntry(instructionIndex, position)); + || ListUtils.last(positionTable).getFromInstructionIndex() < instructionIndex; + + if (!isSimpleLinePosition(position)) { + positionTable.add(new StructuredPositionEntry(instructionIndex, position)); + return true; + } + + // Don't emit simple preamble lines in the table. + if (positionTable.isEmpty() && position.getLine() == 0) { + return false; + } + + // Due to source/synthetic lines we may have non-equal positions with the same simple lines. + if (!positionTable.isEmpty()) { + PositionEntry last = ListUtils.last(positionTable); + if (last instanceof LinePositionEntry) { + if (((LinePositionEntry) last).getLine() == position.getLine()) { + return false; + } + } + } + + positionTable.add(new LinePositionEntry(instructionIndex, position.getLine())); + return true; } private int getConstantIndex(DexItem item) { @@ -269,8 +313,13 @@ public LirBuilder addArgument(int index, boolean knownToBeBoolean) { private int advanceInstructionState() { if (!currentPosition.equals(flushedPosition)) { - setPositionIndex(instructionCount, currentPosition); - flushedPosition = currentPosition; + if (setPositionIndex(instructionCount, currentPosition)) { + flushedPosition = currentPosition; + } + } + if (currentMetadata != null) { + metadataMap.put(instructionCount, currentMetadata); + currentMetadata = null; } return instructionCount++; } @@ -728,7 +777,8 @@ public LirCode build() { tryCatchTable, debugTable, strategy.getStrategyInfo(), - useDexEstimationStrategy); + useDexEstimationStrategy, + metadataMap); } private int getCmpOpcode(NumericType type, Cmp.Bias bias) { diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java index 75a1aa3967..d1a7149df6 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirCode.java +++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java @@ -22,12 +22,12 @@ import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; -import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.code.CatchHandlers; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Position; +import com.android.tools.r8.ir.code.Position.SourcePosition; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.InternalOptions; @@ -41,14 +41,51 @@ public class LirCode extends Code implements Iterable { - public static class PositionEntry { - final int fromInstructionIndex; - final Position position; + public abstract static class PositionEntry { - public PositionEntry(int fromInstructionIndex, Position position) { + private final int fromInstructionIndex; + + PositionEntry(int fromInstructionIndex) { this.fromInstructionIndex = fromInstructionIndex; + } + + public int getFromInstructionIndex() { + return fromInstructionIndex; + } + + public abstract Position getPosition(DexMethod method); + } + + public static class LinePositionEntry extends PositionEntry { + private final int line; + + public LinePositionEntry(int fromInstructionIndex, int line) { + super(fromInstructionIndex); + this.line = line; + } + + public int getLine() { + return line; + } + + @Override + public Position getPosition(DexMethod method) { + return SourcePosition.builder().setMethod(method).setLine(line).build(); + } + } + + public static class StructuredPositionEntry extends PositionEntry { + private final Position position; + + public StructuredPositionEntry(int fromInstructionIndex, Position position) { + super(fromInstructionIndex); this.position = position; } + + @Override + public Position getPosition(DexMethod method) { + return position; + } } public static class TryCatchTable { @@ -119,6 +156,9 @@ public void forEachLocalDefinition(BiConsumer fn) { /** Table of debug local information for each SSA value (if present). */ private final DebugLocalInfoTable debugLocalInfoTable; + /** Table of metadata for each instruction (if present). */ + private final Int2ReferenceMap metadataMap; + public static LirBuilder builder( DexMethod method, LirEncodingStrategy strategy, InternalOptions options) { return new LirBuilder<>(method, strategy, options); @@ -135,7 +175,8 @@ public static LirBuilder builder( TryCatchTable tryCatchTable, DebugLocalInfoTable debugLocalInfoTable, LirStrategyInfo strategyInfo, - boolean useDexEstimationStrategy) { + boolean useDexEstimationStrategy, + Int2ReferenceMap metadataMap) { this.irMetadata = irMetadata; this.constants = constants; this.positionTable = positions; @@ -146,6 +187,7 @@ public static LirBuilder builder( this.debugLocalInfoTable = debugLocalInfoTable; this.strategyInfo = strategyInfo; this.useDexEstimationStrategy = useDexEstimationStrategy; + this.metadataMap = metadataMap; } @SuppressWarnings("unchecked") @@ -238,12 +280,9 @@ public IRCode buildIR( AppView appView, Origin origin, MutableMethodConversionOptions conversionOptions) { - LirCode typedLir = asLirCode(); - return Lir2IRConverter.translate( - method, - typedLir, - LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, null), - appView); + RewrittenPrototypeDescription protoChanges = + appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference()); + return internalBuildIR(method, appView, new NumberGenerator(), null, protoChanges); } @Override @@ -259,21 +298,24 @@ public IRCode buildInliningIR( assert valueNumberGenerator != null; assert callerPosition != null; assert protoChanges != null; + return internalBuildIR(method, appView, valueNumberGenerator, callerPosition, protoChanges); + } + + private IRCode internalBuildIR( + ProgramMethod method, + AppView appView, + NumberGenerator valueNumberGenerator, + Position callerPosition, + RewrittenPrototypeDescription protoChanges) { LirCode typedLir = asLirCode(); - IRCode irCode = - Lir2IRConverter.translate( - method, - typedLir, - LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, valueNumberGenerator), - appView, - valueNumberGenerator, - callerPosition, - protoChanges, - appView.graphLens().getOriginalMethodSignature(method.getReference())); - // TODO(b/225838009): Should we keep track of which code objects need to be narrowed? - // In particular, the encoding of phis does not maintain interfaces. - new TypeAnalysis(appView).narrowing(irCode); - return irCode; + return Lir2IRConverter.translate( + method, + typedLir, + LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, valueNumberGenerator), + appView, + callerPosition, + protoChanges, + appView.graphLens().getOriginalMethodSignature(method.getReference())); } @Override @@ -281,6 +323,9 @@ public void registerCodeReferences(ProgramMethod method, UseRegistry registry) { assert registry.getTraversalContinuation().shouldContinue(); LirUseRegistryCallback registryCallbacks = new LirUseRegistryCallback<>(this, registry); for (LirInstructionView view : this) { + if (metadataMap != null) { + registryCallbacks.setCurrentMetadata(metadataMap.get(view.getInstructionIndex())); + } registryCallbacks.onInstructionView(view); if (registry.getTraversalContinuation().shouldBreak()) { return; @@ -391,9 +436,9 @@ public boolean hasMonitorInstructions() { } @Override - public void forEachPosition(Consumer positionConsumer) { + public void forEachPosition(DexMethod method, Consumer positionConsumer) { for (PositionEntry entry : positionTable) { - positionConsumer.accept(entry.position); + positionConsumer.accept(entry.getPosition(method)); } } } diff --git a/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java b/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java index f0eaeef548..d412abb943 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java +++ b/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java @@ -14,16 +14,41 @@ /** Abstraction for how to decode SSA values (and basic blocks) when reading LIR. */ public abstract class LirDecodingStrategy { + private final boolean useValueIndex; private final NumberGenerator valueNumberGenerator; public LirDecodingStrategy(NumberGenerator valueNumberGenerator) { + assert valueNumberGenerator != null; + this.useValueIndex = valueNumberGenerator.peek() == 0; this.valueNumberGenerator = valueNumberGenerator; } + void reserveValueIndexes(int valuesCount) { + if (useValueIndex) { + for (int i = 0; i < valuesCount; i++) { + valueNumberGenerator.next(); + } + } + } + + public NumberGenerator getValueNumberGenerator() { + return valueNumberGenerator; + } + public final int getValueNumber(int encodedValueIndex) { - return valueNumberGenerator == null ? encodedValueIndex : valueNumberGenerator.next(); + if (useValueIndex) { + assert encodedValueIndex < valueNumberGenerator.peek(); + return encodedValueIndex; + } + return valueNumberGenerator.next(); } + public final V getFreshUnusedValue(TypeElement type) { + return internalGetFreshUnusedValue(valueNumberGenerator.next(), type); + } + + abstract V internalGetFreshUnusedValue(int valueNumber, TypeElement type); + public abstract V getValue(EV encodedValue, LirStrategyInfo strategyInfo); public abstract V getValueDefinitionForInstructionIndex( diff --git a/src/main/java/com/android/tools/r8/lightir/LirStrategy.java b/src/main/java/com/android/tools/r8/lightir/LirStrategy.java index 07ac579740..2f0d90bab9 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirStrategy.java +++ b/src/main/java/com/android/tools/r8/lightir/LirStrategy.java @@ -253,6 +253,7 @@ private static class DecodingStrategy extends LirDecodingStrategy strategyInfo) { @@ -285,6 +286,11 @@ public Value getValue(PhiOrValue encodedValue, LirStrategyInfo strat return value; } + @Override + Value internalGetFreshUnusedValue(int valueNumber, TypeElement type) { + return new Value(valueNumber, type, null); + } + @Override public Value getValueDefinitionForInstructionIndex( int index, TypeElement type, Function getLocalInfo) { diff --git a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java index 67f7a4ec1e..11918fa443 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java +++ b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java @@ -13,18 +13,24 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.graph.UseRegistry.MethodHandleUse; +import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata; import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo; import java.util.List; public class LirUseRegistryCallback extends LirParsedInstructionCallback { private final UseRegistry registry; + private BytecodeInstructionMetadata currentMetadata; public LirUseRegistryCallback(LirCode code, UseRegistry registry) { super(code); this.registry = registry; } + public void setCurrentMetadata(BytecodeInstructionMetadata metadata) { + currentMetadata = metadata; + } + @Override public int getCurrentValueIndex() { // The registry of instructions does not require knowledge of value indexes. @@ -73,7 +79,7 @@ public void onInitClass(DexType clazz) { @Override public void onInstanceGet(DexField field, EV object) { - registry.registerInstanceFieldRead(field); + registry.registerInstanceFieldReadWithMetadata(field, currentMetadata); } @Override @@ -83,7 +89,7 @@ public void onInstancePut(DexField field, EV object, EV value) { @Override public void onStaticGet(DexField field) { - registry.registerStaticFieldRead(field); + registry.registerStaticFieldReadWithMetadata(field, currentMetadata); } @Override diff --git a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java index 6e9366f9d1..0a26062246 100644 --- a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java +++ b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java @@ -99,6 +99,7 @@ private static class DecodingStrategy extends LirDecodingStrategy code, NumberGenerator valueNumberGenerator) { super(valueNumberGenerator); values = new Value[code.getArgumentCount() + code.getInstructionCount()]; + reserveValueIndexes(values.length); } @Override @@ -112,6 +113,11 @@ public Value getValue(Integer encodedValue, LirStrategyInfo strategyInf return value; } + @Override + Value internalGetFreshUnusedValue(int valueNumber, TypeElement type) { + return new Value(valueNumber, type, null); + } + @Override public Value getValueDefinitionForInstructionIndex( int index, TypeElement type, Function getLocalInfo) { 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 09f5a8c3a0..3e1101dd91 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -1693,6 +1693,10 @@ public int getSimpleInliningInstructionLimit() { if (simpleInliningInstructionLimit >= 0) { return simpleInliningInstructionLimit; } + // Allow 2 instructions when using LIR regardless of backend. + if (options.testing.useLir) { + return 2; + } // Allow 3 instructions when generating to class files. if (options.isGeneratingClassFiles()) { return 3; @@ -2082,7 +2086,7 @@ public boolean shouldApplyInliningToInlinee( public static class TestingOptions { public boolean roundtripThroughLir = false; - private boolean useLir = false; + private boolean useLir = System.getProperty("com.android.tools.r8.uselir") != null; public void enableLir() { useLir = true; diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java index 6503576295..049bfdfe87 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java @@ -122,7 +122,8 @@ private void generateR8Version(Path out, Path mapFile, boolean inlining) throws o.enableClassInlining = false; o.inlinerOptions().enableInlining = inlining; o.inlinerOptions().enableInliningOfInvokesWithNullableReceivers = false; - o.inlinerOptions().simpleInliningInstructionLimit = 6; + o.inlinerOptions().simpleInliningInstructionLimit = 7; + o.testing.enableLir(); o.testing.horizontallyMergedClassesConsumer = this::fixInliningNullabilityClass; o.testing.horizontalClassMergingTarget = (appView, candidates, target) -> { diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java index 0de5624581..358497533c 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java @@ -122,6 +122,7 @@ public void testDataclassCopyIsRemovedIfNotUsed() throws Exception { @Test public void testDataclassCopyDefaultIsRemovedIfNotUsed() throws Exception { + boolean useLir = true; String mainClassName = "dataclass.MainCopyKt"; MethodSignature testMethodSignature = new MethodSignature("testDataClassCopyWithDefault", "void", Collections.emptyList()); @@ -131,13 +132,23 @@ public void testDataclassCopyDefaultIsRemovedIfNotUsed() throws Exception { testBuilder -> testBuilder .addKeepRules(keepClassMethod(mainClassName, testMethodSignature)) + .addOptionsModification( + o -> { + if (useLir) { + o.testing.enableLir(); + } else { + o.testing.disableLir(); + } + }) .addOptionsModification(disableClassInliner)) .inspect( inspector -> { // TODO(b/210828502): Investigate why Person is not removed with kotlin 1.7 and 1.8. + // It looks like this is related to size estimates as using LIR changes the result. if (allowAccessModification && (kotlinc.isOneOf(KOTLINC_1_5_0, KOTLINC_1_6_0) - || testParameters.isDexRuntime())) { + || testParameters.isDexRuntime() + || useLir)) { checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName()); } else { ClassSubject dataClass = From 3232b654f8432d9f8cc0a501afdc2bed8bbde0ec Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Tue, 20 Jun 2023 10:37:25 +0200 Subject: [PATCH 143/153] Disallow simplifying arrays to interface types if not exact type match Bug: b/283715197 Change-Id: Ie11bc1f834f3e30d23ae584ade0e006241e4b54b --- .../passes/ArrayConstructionSimplifier.java | 2 +- .../arrays/SimplifyArrayConstructionTest.java | 24 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java index d6c709b9fd..3176d29a0d 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java @@ -430,7 +430,7 @@ private boolean checkTypeOfArrayPut(ArrayPut arrayPut, DexType elementType) { if (clazz == null) { return false; } - return clazz.isInterface() || valueType.isClassType(elementType); + return valueType.isClassType(elementType); } private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) { diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java index 76fcda9f45..9f67d23c91 100644 --- a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java +++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java @@ -228,22 +228,24 @@ private void inspect(CodeInspector inspector, boolean isR8) { assertArrayTypes(twoDimensionalArrays, DexNewArray.class); assertArrayTypes(assumedValues, DexNewArray.class); } else { - assertArrayTypes(referenceArraysNoCasts, DexFilledNewArray.class); - if (isR8 && parameters.canUseSubTypesInFilledNewArray()) { - assertArrayTypes(referenceArraysWithSubclasses, DexFilledNewArray.class); + if (parameters.canUseSubTypesInFilledNewArray()) { + assertArrayTypes(referenceArraysNoCasts, DexFilledNewArray.class); } else { - assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class); + assertArrayTypes(referenceArraysNoCasts, DexNewArray.class, DexFilledNewArray.class); } - if (isR8) { + if (isR8 && parameters.canUseSubTypesInFilledNewArray()) { + assertArrayTypes(referenceArraysWithSubclasses, DexFilledNewArray.class); assertArrayTypes(referenceArraysWithInterfaceImplementations, DexFilledNewArray.class); } else { + assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class); assertArrayTypes(referenceArraysWithInterfaceImplementations, DexNewArray.class); } // TODO(b/246971330): Add support for arrays whose values have conditionals. // assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class); - assertArrayTypes(objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class); + assertArrayTypes( + objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class, DexNewArray.class); if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) { assertArrayTypes(twoDimensionalArrays, DexFilledNewArray.class); @@ -274,12 +276,14 @@ private static Predicate isInstruction(Class clazz) { return isInstruction(Arrays.asList(clazz)); } - private static void assertArrayTypes(MethodSubject method, Class allowedArrayInst) { + private static void assertArrayTypes(MethodSubject method, Class... allowedArrayInst) { assertTrue(method.isPresent()); List> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS); - disallowedClasses.remove(allowedArrayInst); - - assertTrue(method.streamInstructions().anyMatch(isInstruction(allowedArrayInst))); + for (Class allowedArr : allowedArrayInst) { + disallowedClasses.remove(allowedArr); + } + assertTrue( + method.streamInstructions().anyMatch(isInstruction(Arrays.asList(allowedArrayInst)))); assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses))); } From 35c65e7e0602c5c69ef56704e54795bec5f58312 Mon Sep 17 00:00:00 2001 From: Morten Krogh-Jespersen Date: Fri, 16 Jun 2023 12:52:38 +0200 Subject: [PATCH 144/153] Use plugin to define local repositories Bug: b/270105162 Change-Id: I569f3dd6edb0945622ae32c9da458558ec32c356 --- commonBuildSrc/settings.gradle.kts | 27 ------ .../commonBuildSrc}/build.gradle.kts | 0 d8_r8/commonBuildSrc/settings.gradle.kts | 27 ++++++ .../src/main/kotlin/DependenciesPlugin.kt | 10 +- d8_r8/keepanno/settings.gradle.kts | 22 ----- d8_r8/main/settings.gradle.kts | 22 ----- d8_r8/r8lib/settings.gradle.kts | 22 ----- d8_r8/settings.gradle.kts | 92 +++++++------------ d8_r8/test/settings.gradle.kts | 22 ----- .../tests_java_8/settings.gradle.kts | 22 ----- 10 files changed, 69 insertions(+), 197 deletions(-) delete mode 100644 commonBuildSrc/settings.gradle.kts rename {commonBuildSrc => d8_r8/commonBuildSrc}/build.gradle.kts (100%) create mode 100644 d8_r8/commonBuildSrc/settings.gradle.kts rename {commonBuildSrc => d8_r8/commonBuildSrc}/src/main/kotlin/DependenciesPlugin.kt (91%) diff --git a/commonBuildSrc/settings.gradle.kts b/commonBuildSrc/settings.gradle.kts deleted file mode 100644 index d26745ce5d..0000000000 --- a/commonBuildSrc/settings.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2023, 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. - -pluginManagement { - repositories { - maven { - url = uri("file:../third_party/dependencies") - } - maven { - url = uri("file:../third_party/dependencies_new") - } - } -} - -dependencyResolutionManagement { - repositories { - maven { - url = uri("file:../third_party/dependencies") - } - maven { - url = uri("file:../third_party/dependencies_new") - } - } -} - -rootProject.name = "common-build-src" diff --git a/commonBuildSrc/build.gradle.kts b/d8_r8/commonBuildSrc/build.gradle.kts similarity index 100% rename from commonBuildSrc/build.gradle.kts rename to d8_r8/commonBuildSrc/build.gradle.kts diff --git a/d8_r8/commonBuildSrc/settings.gradle.kts b/d8_r8/commonBuildSrc/settings.gradle.kts new file mode 100644 index 0000000000..f0a7aba0ec --- /dev/null +++ b/d8_r8/commonBuildSrc/settings.gradle.kts @@ -0,0 +1,27 @@ +// Copyright (c) 2023, 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. + +pluginManagement { + repositories { + maven { + url = uri("file:../../third_party/dependencies") + } + maven { + url = uri("file:../../third_party/dependencies_new") + } + } +} + +dependencyResolutionManagement { + repositories { + maven { + url = uri("file:../../third_party/dependencies") + } + maven { + url = uri("file:../../third_party/dependencies_new") + } + } +} + +rootProject.name = "common-build-src" diff --git a/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt similarity index 91% rename from commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt rename to d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt index 19b48eeac7..a1796c3b81 100644 --- a/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt +++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt @@ -10,10 +10,18 @@ import org.gradle.api.tasks.Exec import java.io.File import java.nio.file.Path import java.nio.file.Paths +import java.net.URI + class DependenciesPlugin: Plugin { override fun apply(target: Project) { - // Intentionally empty + val dependenciesPath = "file:" + + "${target.getRoot().resolve("third_party").resolve("dependencies").getAbsolutePath()}" + val dependenciesNewPath = "file:" + + "${target.getRoot().resolve("third_party").resolve("dependencies_new").getAbsolutePath()}" + val repositories = target.getRepositories() + repositories.maven { name = "LOCAL_MAVEN_REPO"; url = URI(dependenciesPath) } + repositories.maven { name = "LOCAL_MAVEN_REPO_NEW"; url = URI(dependenciesNewPath) } } } diff --git a/d8_r8/keepanno/settings.gradle.kts b/d8_r8/keepanno/settings.gradle.kts index 3238e39afe..5347ccf66a 100644 --- a/d8_r8/keepanno/settings.gradle.kts +++ b/d8_r8/keepanno/settings.gradle.kts @@ -2,26 +2,4 @@ // 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. -pluginManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } - } -} - -dependencyResolutionManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } - } -} - rootProject.name = "keepanno" diff --git a/d8_r8/main/settings.gradle.kts b/d8_r8/main/settings.gradle.kts index 4745501c7b..765e0f8082 100644 --- a/d8_r8/main/settings.gradle.kts +++ b/d8_r8/main/settings.gradle.kts @@ -2,28 +2,6 @@ // 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. -pluginManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } - } -} - -dependencyResolutionManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } - } -} - rootProject.name = "r8" val root = rootProject.projectDir.parentFile diff --git a/d8_r8/r8lib/settings.gradle.kts b/d8_r8/r8lib/settings.gradle.kts index cb9fc2e45e..b5503c5d33 100644 --- a/d8_r8/r8lib/settings.gradle.kts +++ b/d8_r8/r8lib/settings.gradle.kts @@ -2,28 +2,6 @@ // 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. -pluginManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } - } -} - -dependencyResolutionManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } - } -} - rootProject.name = "r8lib" val root = rootProject.projectDir.parentFile diff --git a/d8_r8/settings.gradle.kts b/d8_r8/settings.gradle.kts index f73899e306..70bb38b000 100644 --- a/d8_r8/settings.gradle.kts +++ b/d8_r8/settings.gradle.kts @@ -4,75 +4,49 @@ // TODO(b/270105162): Move this file out the repository root when old gradle is removed. -pluginManagement { - repositories { - maven { - url = uri("file:../third_party/dependencies") - } - maven { - url = uri("file:../third_party/dependencies_new") - } - } -} - -dependencyResolutionManagement { - repositories { - maven { - url = uri("file:../third_party/dependencies") - } - maven { - url = uri("file:../third_party/dependencies_new") - } - } -} - rootProject.name = "d8-r8" // Bootstrap building by downloading dependencies. - -fun String.execute() = - org.codehaus.groovy.runtime.ProcessGroovyMethods.execute(this) - -fun Process.out() = - String( - this.getInputStream().readAllBytes(), - java.nio.charset.StandardCharsets.UTF_8) -fun Process.err() = - String( - this.getErrorStream().readAllBytes(), - java.nio.charset.StandardCharsets.UTF_8) - val dependencies_bucket = "r8-deps" -val dependencies_sha1_file = "third_party/dependencies.tar.gz.sha1" -var cmd = - ("download_from_google_storage.py --extract" - + " --bucket ${dependencies_bucket}" - + " --sha1_file ${dependencies_sha1_file}") -var process = cmd.execute() -process.waitFor() -if (process.exitValue() != 0) { - throw GradleException( - "Bootstrapping dependencies download failed:" - + "\n${process.err()}\n${process.out()}") +val root = rootProject.projectDir + +fun getRepoRoot() : File { + var current = root + while (!current.getName().equals("d8_r8")) { + current = current.getParentFile() + } + return current.getParentFile() } -val dependencies_new_sha1_file = "third_party/dependencies_new.tar.gz.sha1" -cmd = - ("download_from_google_storage.py --extract" - + " --bucket ${dependencies_bucket}" - + " --sha1_file ${dependencies_new_sha1_file}") -process = cmd.execute() -process.waitFor() -if (process.exitValue() != 0) { + +fun downloadFromGoogleStorage(sha1File : File) { + val cmd = listOf( + "download_from_google_storage.py", + "--extract", + "--bucket", + "${dependencies_bucket}", + "--sha1_file", + "${sha1File}" + ) + println("Executing command: ${cmd.joinToString(" ")}") + var process = ProcessBuilder().command(cmd).start() + process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS) + if (process.exitValue() != 0) { throw GradleException( - "Bootstrapping dependencies_new download failed:" - + "\n${process.err()}\n${process.out()}") + "Bootstrapping dependencies_new download failed:\n" + + "${String(process.getErrorStream().readAllBytes(), + java.nio.charset.StandardCharsets.UTF_8)}\n" + + "${String(process.getInputStream().readAllBytes(), + java.nio.charset.StandardCharsets.UTF_8)}") + } } -val root = rootProject.projectDir +val thirdParty = getRepoRoot().resolve("third_party") +downloadFromGoogleStorage(thirdParty.resolve("dependencies.tar.gz.sha1")) +downloadFromGoogleStorage(thirdParty.resolve("dependencies_new.tar.gz.sha1")) // This project is temporarily located in d8_r8. When moved to root, the parent // folder should just be removed. -includeBuild(root.parentFile.resolve("commonBuildSrc")) +includeBuild(root.resolve("commonBuildSrc")) includeBuild(root.resolve("keepanno")) // We need to include src/main as a composite-build otherwise our test-modules @@ -82,4 +56,4 @@ includeBuild(root.resolve("test")) // Include r8lib as standalone to have a nice separation between source artifacts and r8 compiled // artifacts -includeBuild(root.resolve("r8lib")) \ No newline at end of file +includeBuild(root.resolve("r8lib")) diff --git a/d8_r8/test/settings.gradle.kts b/d8_r8/test/settings.gradle.kts index 5b00aae306..401f47660f 100644 --- a/d8_r8/test/settings.gradle.kts +++ b/d8_r8/test/settings.gradle.kts @@ -2,28 +2,6 @@ // 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. -pluginManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } - } -} - -dependencyResolutionManagement { - repositories { - maven { - url = uri("file:../../third_party/dependencies") - } - maven { - url = uri("file:../../third_party/dependencies_new") - } - } -} - rootProject.name = "r8-tests" val root = rootProject.projectDir.parentFile diff --git a/d8_r8/test_modules/tests_java_8/settings.gradle.kts b/d8_r8/test_modules/tests_java_8/settings.gradle.kts index 592c7d220e..da320a745a 100644 --- a/d8_r8/test_modules/tests_java_8/settings.gradle.kts +++ b/d8_r8/test_modules/tests_java_8/settings.gradle.kts @@ -2,28 +2,6 @@ // 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. -pluginManagement { - repositories { - maven { - url = uri("file:../../../third_party/dependencies") - } - maven { - url = uri("file:../../../third_party/dependencies_new") - } - } -} - -dependencyResolutionManagement { - repositories { - maven { - url = uri("file:../../../third_party/dependencies") - } - maven { - url = uri("file:../../../third_party/dependencies_new") - } - } -} - rootProject.name = "tests_java_8" val root = rootProject.projectDir.parentFile.parentFile From d84f5170ccdfa647af7ee7c603a6654437db5452 Mon Sep 17 00:00:00 2001 From: Christoffer Adamsen Date: Tue, 20 Jun 2023 11:47:07 +0000 Subject: [PATCH 145/153] Revert "Reland "Make ThrowCatchOptimizer a CodeRewriterPass"" This reverts commit d0a1faaa12bdef153d0cef569cdd9cc6fd7af6e6. Reason for revert: Bot failures Change-Id: Icc822351c69094514b5376ef5d0bb02fa5d9d601 --- .../tools/r8/ir/conversion/IRConverter.java | 26 +- .../conversion/passes/BranchSimplifier.java | 100 ++----- .../passes/RedundantConstNumberRemover.java | 278 ------------------ .../passes/SwitchCaseEliminator.java | 6 +- .../passes/ThrowCatchOptimizer.java | 48 +-- .../passes/result/CodeRewriterResult.java | 5 - .../tools/r8/ir/optimize/CodeRewriter.java | 243 +++++++++++++++ .../tools/r8/ir/optimize/DeadCodeRemover.java | 6 +- .../optimize/classinliner/ClassInliner.java | 2 +- .../shaking/EnqueuerDeferredTracingImpl.java | 2 +- 10 files changed, 302 insertions(+), 414 deletions(-) delete mode 100644 src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 44656cf1f3..caf74c7b82 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -38,7 +38,6 @@ import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.ir.conversion.passes.NaturalIntLoopRemover; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; -import com.android.tools.r8.ir.conversion.passes.RedundantConstNumberRemover; import com.android.tools.r8.ir.conversion.passes.SplitBranch; import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer; import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover; @@ -764,16 +763,21 @@ Timing optimize( new StringBuilderAppendOptimizer(appView).run(code, timing); } new SparseConditionalConstantPropagation(appView, code).run(code, timing); - new ThrowCatchOptimizer(appView, isDebugMode).run(code, timing); - if (new BranchSimplifier(appView) - .run(code, timing) - .asControlFlowSimplificationResult() - .anyAffectedValues()) { + timing.begin("Rewrite always throwing instructions"); + new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(code); + timing.end(); + timing.begin("Simplify control flow"); + if (new BranchSimplifier(appView).simplifyBranches(code)) { new TrivialCheckCastAndInstanceOfRemover(appView) .run(code, methodProcessor, methodProcessingContext, timing); } + timing.end(); splitBranch.run(code, timing); - new RedundantConstNumberRemover(appView).run(code, timing); + if (options.enableRedundantConstNumberOptimization) { + timing.begin("Remove const numbers"); + codeRewriter.redundantConstNumberRemoval(code); + timing.end(); + } if (RedundantFieldLoadAndStoreElimination.shouldRun(appView, code)) { timing.begin("Remove field loads"); new RedundantFieldLoadAndStoreElimination(appView, code).run(); @@ -787,6 +791,13 @@ Timing optimize( invertConditionalsForTesting(code); } + if (!isDebugMode) { + timing.begin("Rewrite throw NPE"); + new ThrowCatchOptimizer(appView).rewriteThrowNullPointerException(code); + timing.end(); + previous = printMethod(code, "IR after rewrite throw null (SSA)", previous); + } + timing.begin("Optimize class initializers"); ClassInitializerDefaultsResult classInitializerDefaultsResult = classInitializerDefaultsOptimization.optimize(code, feedback); @@ -926,6 +937,7 @@ Timing optimize( // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL. assert code.verifyNoNullabilityBottomTypes(); + assert code.verifyTypes(appView); previous = diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index 44c9dddf15..28ddb0119a 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java @@ -4,10 +4,6 @@ package com.android.tools.r8.ir.conversion.passes; -import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.NO_CHANGE; -import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.create; - -import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; @@ -39,10 +35,10 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.code.Xor; -import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOutputMode; import com.android.tools.r8.utils.LongInterval; import com.google.common.collect.ImmutableList; @@ -63,27 +59,20 @@ import java.util.PriorityQueue; import java.util.Set; -public class BranchSimplifier extends CodeRewriterPass { - - public BranchSimplifier(AppView appView) { - super(appView); - } +public class BranchSimplifier { - @Override - protected String getTimingId() { - return "BranchSimplifier"; - } + private final AppView appView; + private final InternalOptions options; - @Override - protected boolean shouldRewriteCode(IRCode code) { - return true; + public BranchSimplifier(AppView appView) { + this.appView = appView; + this.options = appView.options(); } - @Override - protected CodeRewriterResult rewriteCode(IRCode code) { - ControlFlowSimplificationResult switchResult = rewriteSwitch(code); - ControlFlowSimplificationResult ifResult = simplifyIf(code); - return switchResult.combine(ifResult); + public boolean simplifyBranches(IRCode code) { + boolean anyAffectedValues = rewriteSwitch(code); + anyAffectedValues |= simplifyIf(code).anyAffectedValues(); + return anyAffectedValues; } public ControlFlowSimplificationResult simplifyIf(IRCode code) { @@ -137,26 +126,10 @@ public ControlFlowSimplificationResult simplifyIf(IRCode code) { } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - return create(!affectedValues.isEmpty(), simplified); + return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified); } - public static class ControlFlowSimplificationResult implements CodeRewriterResult { - - static ControlFlowSimplificationResult create( - boolean anyAffectedValues, boolean anySimplifications) { - if (anyAffectedValues) { - assert anySimplifications; - return ALL_CHANGED; - } - return anySimplifications ? ONLY_SIMPLIFICATIONS : NO_CHANGE; - } - - static final ControlFlowSimplificationResult ALL_CHANGED = - new ControlFlowSimplificationResult(true, true); - static final ControlFlowSimplificationResult ONLY_SIMPLIFICATIONS = - new ControlFlowSimplificationResult(false, true); - static final ControlFlowSimplificationResult NO_CHANGE = - new ControlFlowSimplificationResult(false, false); + public static class ControlFlowSimplificationResult { private final boolean anyAffectedValues; private final boolean anySimplifications; @@ -167,11 +140,6 @@ private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySi this.anySimplifications = anySimplifications; } - @Override - public ControlFlowSimplificationResult asControlFlowSimplificationResult() { - return this; - } - public boolean anyAffectedValues() { return anyAffectedValues; } @@ -179,18 +147,6 @@ public boolean anyAffectedValues() { public boolean anySimplifications() { return anySimplifications; } - - @Override - public boolean hasChanged() { - assert !anyAffectedValues || anySimplifications; - return anySimplifications(); - } - - public ControlFlowSimplificationResult combine(ControlFlowSimplificationResult ifResult) { - return create( - anyAffectedValues || ifResult.anyAffectedValues, - anySimplifications || ifResult.anySimplifications); - } } private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) { @@ -611,25 +567,22 @@ private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) { return true; } - private ControlFlowSimplificationResult rewriteSwitch(IRCode code) { + private boolean rewriteSwitch(IRCode code) { return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance()); } - private ControlFlowSimplificationResult rewriteSwitch( - IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { if (!options.isSwitchRewritingEnabled()) { - return NO_CHANGE; + return false; } if (!code.metadata().mayHaveSwitch()) { - return NO_CHANGE; + return false; } return rewriteSwitchFull(code, switchCaseAnalyzer); } - private ControlFlowSimplificationResult rewriteSwitchFull( - IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { + private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) { boolean needToRemoveUnreachableBlocks = false; - boolean anySimplifications = false; ListIterator blocksIterator = code.listIterator(); while (blocksIterator.hasNext()) { BasicBlock block = blocksIterator.next(); @@ -641,7 +594,6 @@ private ControlFlowSimplificationResult rewriteSwitchFull( if (options.testing.enableDeadSwitchCaseElimination) { SwitchCaseEliminator eliminator = removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer); - anySimplifications |= eliminator.canBeOptimized(); if (eliminator.mayHaveIntroducedUnreachableBlocks()) { needToRemoveUnreachableBlocks = true; } @@ -656,8 +608,7 @@ private ControlFlowSimplificationResult rewriteSwitchFull( theSwitch = instruction.asSwitch(); } if (theSwitch.isIntSwitch()) { - anySimplifications |= - rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); + rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch()); } } } @@ -670,13 +621,12 @@ private ControlFlowSimplificationResult rewriteSwitchFull( Set affectedValues = needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of(); - boolean anyAffectedValues = !affectedValues.isEmpty(); - if (anyAffectedValues) { + if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - return create(anyAffectedValues, anySimplifications); + return !affectedValues.isEmpty(); } public void rewriteSingleKeySwitchToIf( @@ -702,19 +652,19 @@ public void rewriteSingleKeySwitchToIf( iterator.replaceCurrentInstruction(replacement); } - private boolean rewriteIntSwitch( + private void rewriteIntSwitch( IRCode code, ListIterator blockIterator, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) { if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) { - return false; + return; } if (theSwitch.numberOfKeys() == 1) { rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch); - return true; + return; } // If there are more than 1 key, we use the following algorithm to find keys to combine. @@ -803,9 +753,7 @@ private boolean rewriteIntSwitch( if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) { convertSwitchToSwitchAndIfs( code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers); - return true; } - return false; } // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java deleted file mode 100644 index 70402ccd47..0000000000 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) 2023, 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.ir.conversion.passes; - -import com.android.tools.r8.graph.AppInfo; -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.ConstNumber; -import com.android.tools.r8.ir.code.DominatorTree; -import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.If; -import com.android.tools.r8.ir.code.IfType; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; -import com.android.tools.r8.utils.LazyBox; -import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; -import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - -/** - * This optimization exploits that we can sometimes learn the constant value of an SSA value that - * flows into an if-eq of if-neq instruction. - * - *

Consider the following example: - * - *

- * 1. if (obj != null) {
- * 2.  return doStuff();
- * 3. }
- * 4. return null;
- * 
- * - *

Since we know that `obj` is null in all blocks that are dominated by the false-target of the - * if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and thereby - * save a const-number instruction. - */ -public class RedundantConstNumberRemover extends CodeRewriterPass { - - public RedundantConstNumberRemover(AppView appView) { - super(appView); - } - - @Override - protected String getTimingId() { - return "RedundantConstNumberRemover"; - } - - @Override - protected boolean shouldRewriteCode(IRCode code) { - return options.enableRedundantConstNumberOptimization && code.metadata().mayHaveConstNumber(); - } - - @Override - protected CodeRewriterResult rewriteCode(IRCode code) { - redundantConstNumberRemoval(code); - return CodeRewriterResult.NONE; - } - - public void redundantConstNumberRemoval(IRCode code) { - if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() - && !appView.options().testing.forceRedundantConstNumberRemoval) { - // See also b/124152497. - return; - } - - LazyBox>> constantsByValue = - new LazyBox<>(() -> getConstantsByValue(code)); - LazyBox dominatorTree = new LazyBox<>(() -> new DominatorTree(code)); - - boolean changed = false; - for (BasicBlock block : code.blocks) { - Instruction lastInstruction = block.getInstructions().getLast(); - if (!lastInstruction.isIf()) { - continue; - } - - If ifInstruction = lastInstruction.asIf(); - IfType type = ifInstruction.getType(); - - Value lhs = ifInstruction.inValues().get(0); - Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null; - - if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) { - // We can only conclude anything from an if-instruction if it is a zero-test or if one of - // the two operands is a constant. - continue; - } - - // If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values - // of the if-instruction from the outcome of the if-instruction. - if (type != IfType.EQ && type != IfType.NE) { - continue; - } - - BasicBlock trueTarget, falseTarget; - if (type == IfType.EQ) { - trueTarget = ifInstruction.getTrueTarget(); - falseTarget = ifInstruction.fallthroughBlock(); - } else { - falseTarget = ifInstruction.getTrueTarget(); - trueTarget = ifInstruction.fallthroughBlock(); - } - - if (ifInstruction.isZeroTest()) { - changed |= - replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree); - if (lhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - 1, lhs, falseTarget, constantsByValue, code, dominatorTree); - } - } else { - assert rhs != null; - if (lhs.isConstNumber()) { - ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber(); - changed |= - replaceDominatedConstNumbers( - lhsAsNumber.getRawValue(), - rhs, - trueTarget, - constantsByValue, - code, - dominatorTree); - if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - negateBoolean(lhsAsNumber), - rhs, - falseTarget, - constantsByValue, - code, - dominatorTree); - } - } else { - assert rhs.isConstNumber(); - ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber(); - changed |= - replaceDominatedConstNumbers( - rhsAsNumber.getRawValue(), - lhs, - trueTarget, - constantsByValue, - code, - dominatorTree); - if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { - changed |= - replaceDominatedConstNumbers( - negateBoolean(rhsAsNumber), - lhs, - falseTarget, - constantsByValue, - code, - dominatorTree); - } - } - } - - if (constantsByValue.computeIfAbsent().isEmpty()) { - break; - } - } - - if (changed) { - code.removeAllDeadAndTrivialPhis(); - } - assert code.isConsistentSSA(appView); - } - - private static Long2ReferenceMap> getConstantsByValue(IRCode code) { - // A map from the raw value of constants in `code` to the const number instructions that define - // the given raw value (irrespective of the type of the raw value). - Long2ReferenceMap> constantsByValue = new Long2ReferenceOpenHashMap<>(); - - // Initialize `constantsByValue`. - for (Instruction instruction : code.instructions()) { - if (instruction.isConstNumber()) { - ConstNumber constNumber = instruction.asConstNumber(); - if (constNumber.outValue().hasLocalInfo()) { - // Not necessarily constant, because it could be changed in the debugger. - continue; - } - long rawValue = constNumber.getRawValue(); - if (constantsByValue.containsKey(rawValue)) { - constantsByValue.get(rawValue).add(constNumber); - } else { - List list = new ArrayList<>(); - list.add(constNumber); - constantsByValue.put(rawValue, list); - } - } - } - return constantsByValue; - } - - private static int negateBoolean(ConstNumber number) { - assert number.outValue().knownToBeBoolean(); - return number.getRawValue() == 0 ? 1 : 0; - } - - private boolean replaceDominatedConstNumbers( - long withValue, - Value newValue, - BasicBlock dominator, - LazyBox>> constantsByValueSupplier, - IRCode code, - LazyBox dominatorTree) { - if (newValue.hasLocalInfo()) { - // We cannot replace a constant with a value that has local info, because that could change - // debugging behavior. - return false; - } - - Long2ReferenceMap> constantsByValue = - constantsByValueSupplier.computeIfAbsent(); - List constantsWithValue = constantsByValue.get(withValue); - if (constantsWithValue == null || constantsWithValue.isEmpty()) { - return false; - } - - boolean changed = false; - - ListIterator constantWithValueIterator = constantsWithValue.listIterator(); - while (constantWithValueIterator.hasNext()) { - ConstNumber constNumber = constantWithValueIterator.next(); - Value value = constNumber.outValue(); - assert !value.hasLocalInfo(); - assert constNumber.getRawValue() == withValue; - - BasicBlock block = constNumber.getBlock(); - - // If the following condition does not hold, then the if-instruction does not dominate the - // block containing the constant, although the true or false target does. - if (block == dominator && block.getPredecessors().size() != 1) { - // This should generally not happen, but it is possible to write bytecode where it does. - assert false; - continue; - } - - if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) { - // We cannot replace a boolean by a none-boolean since that can lead to verification - // errors. For example, the following code fails with "register v1 has type Imprecise - // Constant: 127 but expected Boolean return-1nr". - // - // public boolean convertIntToBoolean(int v1) { - // const/4 v0, 0x1 - // if-eq v1, v0, :eq_true - // const/4 v1, 0x0 - // :eq_true - // return v1 - // } - continue; - } - - if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) { - if (newValue.getType().lessThanOrEqual(value.getType(), appView)) { - value.replaceUsers(newValue); - block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead(); - constantWithValueIterator.remove(); - changed = true; - } else if (value.getType().isNullType()) { - // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of - // the use sites of `value` without introducing a type error. - } - } - } - - if (constantsWithValue.isEmpty()) { - constantsByValue.remove(withValue); - } - - return changed; - } -} diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java index 4a1a1d4e7b..cbda6309d0 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java @@ -45,7 +45,7 @@ private boolean allSwitchCasesMarkedForRemoval() { && switchCasesToBeRemoved.size() == theSwitch.numberOfKeys(); } - boolean canBeOptimized() { + private boolean canBeOptimized() { assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty(); return switchCasesToBeRemoved != null || hasAlwaysHitCase() || !isFallthroughLive(); } @@ -96,7 +96,7 @@ void markSwitchFallthroughAsNeverHit() { liveFallthrough = false; } - void optimize() { + boolean optimize() { if (canBeOptimized()) { int originalNumberOfSuccessors = block.getSuccessors().size(); IntList removedSuccessorIndices = unlinkDeadSuccessors(); @@ -107,7 +107,9 @@ void optimize() { // Replace switch by a new switch where the dead switch cases have been removed. replaceSwitchByOptimizedSwitch(originalNumberOfSuccessors, removedSuccessorIndices); } + return true; } + return false; } private IntList unlinkDeadSuccessors() { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java index 85cd30e1f2..f34a6bd20e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java @@ -5,10 +5,10 @@ package com.android.tools.r8.ir.conversion.passes; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; @@ -32,7 +32,6 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -41,42 +40,18 @@ import java.util.ListIterator; import java.util.Set; -public class ThrowCatchOptimizer extends CodeRewriterPass { +public class ThrowCatchOptimizer { - private final boolean rewriteThrowNull; - - public ThrowCatchOptimizer(AppView appView, boolean isDebug) { - super(appView); - this.rewriteThrowNull = !isDebug; - } + private final AppView appView; + private final DexItemFactory dexItemFactory; public ThrowCatchOptimizer(AppView appView) { - super(appView); - this.rewriteThrowNull = false; - } - - @Override - protected String getTimingId() { - return "ThrowCatchOptimizer"; - } - - @Override - protected boolean shouldRewriteCode(IRCode code) { - return true; - } - - @Override - protected CodeRewriterResult rewriteCode(IRCode code) { - optimizeAlwaysThrowingInstructions(code); - if (rewriteThrowNull) { - rewriteThrowNullPointerException(code); - } - return CodeRewriterResult.NONE; + this.appView = appView; + this.dexItemFactory = appView.dexItemFactory(); } // Rewrite 'throw new NullPointerException()' to 'throw null'. - private void rewriteThrowNullPointerException(IRCode code) { - boolean hasChanged = false; + public void rewriteThrowNullPointerException(IRCode code) { boolean shouldRemoveUnreachableBlocks = false; for (BasicBlock block : code.blocks) { InstructionListIterator it = block.listIterator(code); @@ -89,7 +64,7 @@ private void rewriteThrowNullPointerException(IRCode code) { if (appView .dexItemFactory() .objectsMethods - .isRequireNonNullMethod(code.context().getReference())) { + .isRequireNonNullMethod(code.method().getReference())) { continue; } @@ -151,7 +126,6 @@ private void rewriteThrowNullPointerException(IRCode code) { valueIsNullTarget, throwInstruction.getPosition()); shouldRemoveUnreachableBlocks = true; - hasChanged = true; } // Check for 'new-instance NullPointerException' with 2 users, not declaring a local and @@ -198,7 +172,6 @@ private void rewriteThrowNullPointerException(IRCode code) { // Replace them with 'const 0' and 'throw'. it.add(nullPointer); it.add(throwInstruction); - hasChanged = true; } } } @@ -213,16 +186,13 @@ private void rewriteThrowNullPointerException(IRCode code) { new TypeAnalysis(appView).narrowing(affectedValues); } } - if (hasChanged) { - code.removeRedundantBlocks(); - } assert code.isConsistentSSA(appView); } // Find all instructions that always throw, split the block after each such instruction and follow // it with a block throwing a null value (which should result in NPE). Note that this throw is not // expected to be ever reached, but is intended to satisfy verifier. - private void optimizeAlwaysThrowingInstructions(IRCode code) { + public void optimizeAlwaysThrowingInstructions(IRCode code) { Set affectedValues = Sets.newIdentityHashSet(); Set blocksToRemove = Sets.newIdentityHashSet(); ListIterator blockIterator = code.listIterator(); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java index 1af288a003..68d5c0d2ff 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java @@ -5,7 +5,6 @@ package com.android.tools.r8.ir.conversion.passes.result; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult; public interface CodeRewriterResult { @@ -38,8 +37,4 @@ public boolean hasChanged() { } boolean hasChanged(); - - default ControlFlowSimplificationResult asControlFlowSimplificationResult() { - throw new Unreachable("Not a control flow simplification result."); - } } 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 19f053fa46..e6968798e7 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 @@ -21,9 +21,11 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; import com.android.tools.r8.ir.code.DebugLocalWrite; import com.android.tools.r8.ir.code.DebugLocalsChange; +import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; @@ -38,6 +40,7 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; +import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.common.collect.Streams; @@ -48,7 +51,11 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import java.util.ArrayList; import java.util.List; +import java.util.ListIterator; import java.util.Set; public class CodeRewriter { @@ -202,6 +209,242 @@ public void simplifyDebugLocals(IRCode code) { } } + /** + * This optimization exploits that we can sometimes learn the constant value of an SSA value that + * flows into an if-eq of if-neq instruction. + * + *

Consider the following example: + * + *

+   * 1. if (obj != null) {
+   * 2.  return doStuff();
+   * 3. }
+   * 4. return null;
+   * 
+ * + *

Since we know that `obj` is null in all blocks that are dominated by the false-target of the + * if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and + * thereby save a const-number instruction. + */ + public void redundantConstNumberRemoval(IRCode code) { + if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() + && !appView.options().testing.forceRedundantConstNumberRemoval) { + // See also b/124152497. + return; + } + + if (!code.metadata().mayHaveConstNumber()) { + return; + } + + LazyBox>> constantsByValue = + new LazyBox<>(() -> getConstantsByValue(code)); + LazyBox dominatorTree = new LazyBox<>(() -> new DominatorTree(code)); + + boolean changed = false; + for (BasicBlock block : code.blocks) { + Instruction lastInstruction = block.getInstructions().getLast(); + if (!lastInstruction.isIf()) { + continue; + } + + If ifInstruction = lastInstruction.asIf(); + IfType type = ifInstruction.getType(); + + Value lhs = ifInstruction.inValues().get(0); + Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null; + + if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) { + // We can only conclude anything from an if-instruction if it is a zero-test or if one of + // the two operands is a constant. + continue; + } + + // If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values + // of the if-instruction from the outcome of the if-instruction. + if (type != IfType.EQ && type != IfType.NE) { + continue; + } + + BasicBlock trueTarget, falseTarget; + if (type == IfType.EQ) { + trueTarget = ifInstruction.getTrueTarget(); + falseTarget = ifInstruction.fallthroughBlock(); + } else { + falseTarget = ifInstruction.getTrueTarget(); + trueTarget = ifInstruction.fallthroughBlock(); + } + + if (ifInstruction.isZeroTest()) { + changed |= + replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree); + if (lhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + 1, lhs, falseTarget, constantsByValue, code, dominatorTree); + } + } else { + assert rhs != null; + if (lhs.isConstNumber()) { + ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber(); + changed |= + replaceDominatedConstNumbers( + lhsAsNumber.getRawValue(), + rhs, + trueTarget, + constantsByValue, + code, + dominatorTree); + if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + negateBoolean(lhsAsNumber), + rhs, + falseTarget, + constantsByValue, + code, + dominatorTree); + } + } else { + assert rhs.isConstNumber(); + ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber(); + changed |= + replaceDominatedConstNumbers( + rhsAsNumber.getRawValue(), + lhs, + trueTarget, + constantsByValue, + code, + dominatorTree); + if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { + changed |= + replaceDominatedConstNumbers( + negateBoolean(rhsAsNumber), + lhs, + falseTarget, + constantsByValue, + code, + dominatorTree); + } + } + } + + if (constantsByValue.computeIfAbsent().isEmpty()) { + break; + } + } + + if (changed) { + code.removeAllDeadAndTrivialPhis(); + } + assert code.isConsistentSSA(appView); + } + + private static Long2ReferenceMap> getConstantsByValue(IRCode code) { + // A map from the raw value of constants in `code` to the const number instructions that define + // the given raw value (irrespective of the type of the raw value). + Long2ReferenceMap> constantsByValue = new Long2ReferenceOpenHashMap<>(); + + // Initialize `constantsByValue`. + for (Instruction instruction : code.instructions()) { + if (instruction.isConstNumber()) { + ConstNumber constNumber = instruction.asConstNumber(); + if (constNumber.outValue().hasLocalInfo()) { + // Not necessarily constant, because it could be changed in the debugger. + continue; + } + long rawValue = constNumber.getRawValue(); + if (constantsByValue.containsKey(rawValue)) { + constantsByValue.get(rawValue).add(constNumber); + } else { + List list = new ArrayList<>(); + list.add(constNumber); + constantsByValue.put(rawValue, list); + } + } + } + return constantsByValue; + } + + private static int negateBoolean(ConstNumber number) { + assert number.outValue().knownToBeBoolean(); + return number.getRawValue() == 0 ? 1 : 0; + } + + private boolean replaceDominatedConstNumbers( + long withValue, + Value newValue, + BasicBlock dominator, + LazyBox>> constantsByValueSupplier, + IRCode code, + LazyBox dominatorTree) { + if (newValue.hasLocalInfo()) { + // We cannot replace a constant with a value that has local info, because that could change + // debugging behavior. + return false; + } + + Long2ReferenceMap> constantsByValue = + constantsByValueSupplier.computeIfAbsent(); + List constantsWithValue = constantsByValue.get(withValue); + if (constantsWithValue == null || constantsWithValue.isEmpty()) { + return false; + } + + boolean changed = false; + + ListIterator constantWithValueIterator = constantsWithValue.listIterator(); + while (constantWithValueIterator.hasNext()) { + ConstNumber constNumber = constantWithValueIterator.next(); + Value value = constNumber.outValue(); + assert !value.hasLocalInfo(); + assert constNumber.getRawValue() == withValue; + + BasicBlock block = constNumber.getBlock(); + + // If the following condition does not hold, then the if-instruction does not dominate the + // block containing the constant, although the true or false target does. + if (block == dominator && block.getPredecessors().size() != 1) { + // This should generally not happen, but it is possible to write bytecode where it does. + assert false; + continue; + } + + if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) { + // We cannot replace a boolean by a none-boolean since that can lead to verification + // errors. For example, the following code fails with "register v1 has type Imprecise + // Constant: 127 but expected Boolean return-1nr". + // + // public boolean convertIntToBoolean(int v1) { + // const/4 v0, 0x1 + // if-eq v1, v0, :eq_true + // const/4 v1, 0x0 + // :eq_true + // return v1 + // } + continue; + } + + if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) { + if (newValue.getType().lessThanOrEqual(value.getType(), appView)) { + value.replaceUsers(newValue); + block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead(); + constantWithValueIterator.remove(); + changed = true; + } else if (value.getType().isNullType()) { + // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of + // the use sites of `value` without introducing a type error. + } + } + } + + if (constantsWithValue.isEmpty()) { + constantsByValue.remove(withValue); + } + + return changed; + } + /** * Remove moves that are not actually used by instructions in exiting paths. These moves can arise * due to debug local info needing a particular value and the live-interval for it then moves it diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index 94fabb76be..e9b180c578 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java @@ -60,15 +60,11 @@ public void run(IRCode code, Timing timing) { removeDeadInstructions(worklist, code, block, valueIsDeadAnalysis); removeDeadPhis(worklist, block, valueIsDeadAnalysis); } - } while (branchSimplifier - .simplifyIf(code) - .asControlFlowSimplificationResult() - .anySimplifications() + } while (branchSimplifier.simplifyIf(code).anySimplifications() || removeUnneededCatchHandlers(code)); code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); - assert verifyNoDeadCode(code); timing.end(); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index b46d0a5bfa..d3d2a08bff 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java @@ -252,7 +252,7 @@ public final void processMethodCode( new TrivialCheckCastAndInstanceOfRemover(appView) .run(code, methodProcessor, methodProcessingContext, Timing.empty()); // If a method was inlined we may be able to prune additional branches. - new BranchSimplifier(appView).run(code, Timing.empty()); + new BranchSimplifier(appView).simplifyBranches(code); // If a method was inlined we may see more trivial computation/conversion of String. boolean isDebugMode = appView.options().debug || method.getOrComputeReachabilitySensitive(appView); diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java index f7cf0b5a21..1c0d3de9d7 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java @@ -270,7 +270,7 @@ private void rewriteMethod( rewriter.rewriteCode(ir, initializedClassesWithContexts, prunedFields); // Run dead code elimination. - new ThrowCatchOptimizer(appView).run(ir, Timing.empty()); + new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(ir); rewriter.getDeadCodeRemover().run(ir, Timing.empty()); // Finalize to class files or dex. From 16c667a7694fab6b682da5e79d41902f3474b9d0 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 20 Jun 2023 14:10:45 +0200 Subject: [PATCH 146/153] Preserve IllegalAccessError in access modifier Bug: b/278687711 Change-Id: Ibdd66a8c7843e4964c0bce01c9bdd3fcef19840c --- .../android/tools/r8/cf/LoadStoreHelper.java | 9 +- .../com/android/tools/r8/graph/DexClass.java | 5 ++ .../tools/r8/graph/FieldResolutionResult.java | 4 + .../android/tools/r8/graph/ProgramMethod.java | 2 +- .../GeneratedMessageLiteBuilderShrinker.java | 14 ++- .../tools/r8/ir/conversion/IRConverter.java | 1 - .../InlineCandidateProcessor.java | 13 +-- .../R8MemberValuePropagation.java | 4 + .../accessmodification/AccessModifier.java | 13 ++- ...ntPropagatorOptimizationInfoPopulator.java | 2 +- .../tools/r8/shaking/AppInfoWithLiveness.java | 20 ++++- .../android/tools/r8/shaking/Enqueuer.java | 83 +++++++++++++----- .../shaking/EnqueuerDeferredTracingImpl.java | 4 + ...eserveIllegalAccessErrorCheckCastTest.java | 83 ++++++++++++++++++ ...serveIllegalAccessErrorConstClassTest.java | 85 +++++++++++++++++++ ...rrorInitialMethodResolutionHolderTest.java | 85 +++++++++++++++++++ ...veIllegalAccessErrorInstanceFieldTest.java | 72 ++++++++++++++++ ...serveIllegalAccessErrorInstanceOfTest.java | 83 ++++++++++++++++++ .../PreserveIllegalAccessErrorMethodTest.java | 72 ++++++++++++++++ ...erveIllegalAccessErrorStaticFieldTest.java | 72 ++++++++++++++++ .../PrivateOverrideOfPublicMethodTest.java | 3 +- 21 files changed, 692 insertions(+), 37 deletions(-) create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorCheckCastTest.java create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorConstClassTest.java create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInitialMethodResolutionHolderTest.java create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInstanceFieldTest.java create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInstanceOfTest.java create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorMethodTest.java create mode 100644 src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorStaticFieldTest.java diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java index f180e60062..c7c4cc729e 100644 --- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java +++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java @@ -88,10 +88,11 @@ private static boolean isConstInstructionAlwaysThreeBytes(ConstInstruction instr return false; } - private static boolean canRemoveConstInstruction(ConstInstruction instr, BasicBlock block) { + private boolean canRemoveConstInstruction(ConstInstruction instr, BasicBlock block) { Value value = instr.outValue(); return !hasLocalInfoOrUsersOutsideThisBlock(value, block) - && (value.numberOfUsers() <= 1 || !isConstInstructionAlwaysThreeBytes(instr)); + && (value.numberOfUsers() <= 1 || !isConstInstructionAlwaysThreeBytes(instr)) + && !instr.instructionInstanceCanThrow(appView, code.context()); } public void insertLoadsAndStores() { @@ -181,7 +182,9 @@ public void storeOutValue(Instruction instruction, InstructionListIterator it) { it.removeOrReplaceByDebugLocalRead(); return; } - assert instruction.outValue().isUsed(); // Should have removed it above. + assert instruction.outValue().isUsed() + || instruction.instructionInstanceCanThrow(appView, code.context()) + : "Expected instruction to be removed: " + instruction; } if (!instruction.outValue().isUsed()) { popOutValue(instruction.outValue(), instruction, it); diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index 30909c5e30..cc8e00f55b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java @@ -583,6 +583,11 @@ public boolean canBeInstantiatedByNewInstance() { return !isAbstract() && !isAnnotation() && !isInterface(); } + public OptionalBool isAccessibleFrom( + ProgramDefinition context, AppView appView) { + return AccessControl.isClassAccessible(this, context, appView); + } + public boolean isAbstract() { return accessFlags.isAbstract(); } diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java index 9a6d7cb9e7..ac9ff709fa 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java +++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java @@ -42,6 +42,10 @@ public DexField getResolvedFieldReference() { return null; } + public DexClass getResolvedHolder() { + return null; + } + @Override public DexClassAndField getResolutionPair() { return null; diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java index 99879123c2..ff6d789540 100644 --- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java +++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java @@ -85,7 +85,7 @@ public boolean canBeConvertedToAbstractMethod(AppView appVi && !getAccessFlags().isPrivate() && !getAccessFlags().isStatic() && !getDefinition().isInstanceInitializer() - && !appView.appInfo().isFailedResolutionTarget(getReference()); + && !appView.appInfo().isFailedMethodResolutionTarget(getReference()); } public void convertToAbstractOrThrowNullMethod(AppView appView) { diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java index 20ac50a4b0..16a8fadc5b 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java @@ -16,6 +16,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.SubtypingInfo; import com.android.tools.r8.graph.analysis.EnqueuerAnalysis; @@ -135,10 +136,21 @@ public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing // This builder class is never used in the program except from dynamicMethod(), // which creates an instance of the builder. Instead of creating an instance of the // builder class, we just instantiate the parent builder class. For this to work, - // we make the parent builder non-abstract. + // we make the parent builder non-abstract and its constructor public. DexProgramClass superClass = asProgramClassOrNull(appView.definitionFor(builder.superType)); assert superClass != null; + + ProgramMethod constructorMethod = + superClass.lookupProgramMethod( + references.generatedMessageLiteBuilderMethods.constructorMethod); + MethodAccessFlags constructorFlags = constructorMethod.getAccessFlags(); + if (!constructorFlags.isPublic()) { + constructorFlags.unsetPrivate(); + constructorFlags.unsetProtected(); + constructorFlags.setPublic(); + } + superClass.accessFlags.demoteFromAbstract(); if (superClass.type == references.generatedMessageLiteBuilderType) { // Manually trace `new GeneratedMessageLite.Builder(DEFAULT_INSTANCE)` since we diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index caf74c7b82..7e0cf3a1b5 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -1065,7 +1065,6 @@ public void finalizeIR( OptimizationFeedback feedback, BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing) { - IRCode oldCode = code; if (options.testing.roundtripThroughLir) { code = roundtripThroughLir(code, feedback, bytecodeMetadataProvider, timing); } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java index 088c331210..52eaa3aebf 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java @@ -19,6 +19,7 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.FieldResolutionResult; import com.android.tools.r8.graph.FieldResolutionResult.SingleProgramFieldResolutionResult; import com.android.tools.r8.graph.LibraryMethod; import com.android.tools.r8.graph.MethodResolutionResult; @@ -255,11 +256,13 @@ InstructionOrPhi areInstanceUsersEligible(LazyBox defaultOracle) } if (user.isInstanceGet()) { - DexEncodedField field = - appView - .appInfo() - .resolveField(user.asFieldInstruction().getField()) - .getResolvedField(); + FieldResolutionResult resolutionResult = + appView.appInfo().resolveField(user.asFieldInstruction().getField()); + if (!resolutionResult.isSingleFieldResolutionResult() + || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) { + return user; // Not eligible. + } + DexEncodedField field = resolutionResult.getResolvedField(); if (field == null || field.isStatic()) { return user; // Not eligible. } diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java index b8d6c66c7a..9370d5a000 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java @@ -245,6 +245,10 @@ private void rewriteFieldGet( return; } + if (resolutionResult.isAccessibleFrom(code.context(), appView).isPossiblyFalse()) { + return; + } + DexClassAndField target = resolutionResult.getResolutionPair(); DexEncodedField definition = target.getDefinition(); if (definition.isStatic() != current.isStaticGet()) { diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java index ea25b09a88..f5482db910 100644 --- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java @@ -255,8 +255,17 @@ private DexMethod getAndReserveNewMethodReference( } private boolean isAccessModificationAllowed(ProgramDefinition definition) { - // TODO(b/278687711): Also check that the definition does not have any illegal accesses to it. - return appView.getKeepInfo(definition).isAccessModificationAllowed(options); + if (!appView.getKeepInfo(definition).isAccessModificationAllowed(options)) { + return false; + } + if (definition.isClass()) { + return !appView.appInfo().isFailedClassResolutionTarget(definition.asClass().getType()); + } + if (definition.isField()) { + return !appView.appInfo().isFailedFieldResolutionTarget(definition.asField().getReference()); + } + assert definition.isMethod(); + return !appView.appInfo().isFailedMethodResolutionTarget(definition.asMethod().getReference()); } private boolean isRenamingAllowed(ProgramMethod method) { diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java index f4a7af29a4..0991b0f7d5 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java @@ -182,7 +182,7 @@ private void setOptimizationInfo(ProgramMethod method, ProgramMethodSet prunedMe if (method.getDefinition().belongsToDirectPool() && !method.getOptimizationInfo().returnValueHasBeenPropagated() && !method.getDefinition().getGenericSignature().hasSignature() - && !appView.appInfo().isFailedResolutionTarget(method.getReference())) { + && !appView.appInfo().isFailedMethodResolutionTarget(method.getReference())) { prunedMethods.add(method); } else if (method.getDefinition().hasCode()) { method.convertToAbstractOrThrowNullMethod(appView); diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java index 05112d6c34..d3ed9d1297 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java @@ -109,6 +109,9 @@ public class AppInfoWithLiveness extends AppInfoWithClassHierarchy */ private Set targetedMethods; + /** Classes that lead to resolution errors such as non-existing or invalid targets. */ + private final Set failedClassResolutionTargets; + /** Method targets that lead to resolution errors such as non-existing or invalid targets. */ private final Set failedMethodResolutionTargets; @@ -209,6 +212,7 @@ public class AppInfoWithLiveness extends AppInfoWithClassHierarchy Set deadProtoTypes, Set liveTypes, Set targetedMethods, + Set failedClassResolutionTargets, Set failedMethodResolutionTargets, Set failedFieldResolutionTargets, Set bootstrapMethods, @@ -241,6 +245,7 @@ public class AppInfoWithLiveness extends AppInfoWithClassHierarchy this.deadProtoTypes = deadProtoTypes; this.liveTypes = liveTypes; this.targetedMethods = targetedMethods; + this.failedClassResolutionTargets = failedClassResolutionTargets; this.failedMethodResolutionTargets = failedMethodResolutionTargets; this.failedFieldResolutionTargets = failedFieldResolutionTargets; this.bootstrapMethods = bootstrapMethods; @@ -281,6 +286,7 @@ private AppInfoWithLiveness(AppInfoWithLiveness previous, CommittedItems committ previous.deadProtoTypes, CollectionUtils.addAll(previous.liveTypes, committedItems.getCommittedProgramTypes()), previous.targetedMethods, + previous.failedClassResolutionTargets, previous.failedMethodResolutionTargets, previous.failedFieldResolutionTargets, previous.bootstrapMethods, @@ -324,6 +330,7 @@ private AppInfoWithLiveness( previous.deadProtoTypes, pruneClasses(previous.liveTypes, prunedItems, executorService, futures), pruneMethods(previous.targetedMethods, prunedItems, executorService, futures), + pruneClasses(previous.failedClassResolutionTargets, prunedItems, executorService, futures), pruneMethods(previous.failedMethodResolutionTargets, prunedItems, executorService, futures), pruneFields(previous.failedFieldResolutionTargets, prunedItems, executorService, futures), pruneMethods(previous.bootstrapMethods, prunedItems, executorService, futures), @@ -530,6 +537,7 @@ public AppInfoWithLiveness rebuildWithMainDexInfo(MainDexInfo mainDexInfo) { deadProtoTypes, liveTypes, targetedMethods, + failedClassResolutionTargets, failedMethodResolutionTargets, failedFieldResolutionTargets, bootstrapMethods, @@ -607,6 +615,7 @@ public AppInfoWithLiveness( this.deadProtoTypes = previous.deadProtoTypes; this.liveTypes = previous.liveTypes; this.targetedMethods = previous.targetedMethods; + this.failedClassResolutionTargets = previous.failedClassResolutionTargets; this.failedMethodResolutionTargets = previous.failedMethodResolutionTargets; this.failedFieldResolutionTargets = previous.failedFieldResolutionTargets; this.bootstrapMethods = previous.bootstrapMethods; @@ -711,7 +720,11 @@ public boolean isTargetedMethod(DexMethod method) { return targetedMethods.contains(method); } - public boolean isFailedResolutionTarget(DexMethod method) { + public boolean isFailedClassResolutionTarget(DexType type) { + return failedClassResolutionTargets.contains(type); + } + + public boolean isFailedMethodResolutionTarget(DexMethod method) { return failedMethodResolutionTargets.contains(method); } @@ -719,6 +732,10 @@ public Set getFailedMethodResolutionTargets() { return failedMethodResolutionTargets; } + public boolean isFailedFieldResolutionTarget(DexField field) { + return failedFieldResolutionTargets.contains(field); + } + public Set getFailedFieldResolutionTargets() { return failedFieldResolutionTargets; } @@ -1185,6 +1202,7 @@ public AppInfoWithLiveness rewrittenWithLens( deadProtoTypes, lens.rewriteReferences(liveTypes), lens.rewriteReferences(targetedMethods), + lens.rewriteReferences(failedClassResolutionTargets), lens.rewriteReferences(failedMethodResolutionTargets), lens.rewriteFields(failedFieldResolutionTargets, timing), lens.rewriteReferences(bootstrapMethods), diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index e01f063888..de7943fbb2 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java @@ -27,6 +27,7 @@ import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.experimental.graphinfo.GraphConsumer; +import com.android.tools.r8.graph.AccessControl; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ClassDefinition; @@ -355,6 +356,9 @@ public boolean isWhyAreYouKeeping() { */ private final LiveMethodsSet targetedMethods; + /** Set of classes that have invalid resolutions or loookups. */ + private final Set failedClassResolutionTargets; + /** Set of methods that have invalid resolutions or lookups. */ private final Set failedMethodResolutionTargets; @@ -505,6 +509,7 @@ public boolean isWhyAreYouKeeping() { } targetedMethods = new LiveMethodsSet(graphReporter::registerMethod); + failedClassResolutionTargets = SetUtils.newIdentityHashSet(0); // This set is only populated in edge cases due to multiple default interface methods. // The set is generally expected to be empty and in the unlikely chance it is not, it will // likely contain two methods. Thus the default capacity of 2. @@ -1290,18 +1295,15 @@ private boolean isConstClassMaybeUsedAsLock( private void internalTraceConstClassOrCheckCast( DexType type, ProgramMethod currentMethod, boolean ignoreCompatRules) { + DexProgramClass baseClass = resolveBaseType(type, currentMethod); traceTypeReference(type, currentMethod); if (!forceProguardCompatibility || ignoreCompatRules) { return; } - DexType baseType = type.toBaseType(appView.dexItemFactory()); - if (baseType.isClassType()) { - DexProgramClass baseClass = getProgramClassOrNull(baseType, currentMethod); - if (baseClass != null) { - // Don't require any constructor, see b/112386012. - markClassAsInstantiatedWithCompatRule( - baseClass, () -> graphReporter.reportCompatInstantiated(baseClass, currentMethod)); - } + if (baseClass != null) { + // Don't require any constructor, see b/112386012. + markClassAsInstantiatedWithCompatRule( + baseClass, () -> graphReporter.reportCompatInstantiated(baseClass, currentMethod)); } } @@ -1395,6 +1397,7 @@ void traceTypeReference(DexType type, ProgramMethod currentMethod) { void traceInstanceOf(DexType type, ProgramMethod currentMethod) { instanceOfAnalyses.forEach(analysis -> analysis.traceInstanceOf(type, currentMethod)); + resolveBaseType(type, currentMethod); traceTypeReference(type, currentMethod); } @@ -2334,11 +2337,27 @@ private boolean shouldKeepAnnotation( appView, annotatedItem, annotation, isLive, annotatedKind); } + private DexProgramClass resolveBaseType(DexType type, ProgramDefinition context) { + if (type.isArrayType()) { + return resolveBaseType(type.toBaseType(appView.dexItemFactory()), context); + } + if (type.isClassType()) { + DexProgramClass clazz = + asProgramClassOrNull(appView.definitionFor(type, context.getContextClass())); + if (clazz != null) { + checkAccess(clazz, context); + } + return clazz; + } + return null; + } + private FieldResolutionResult resolveField(DexField field, ProgramDefinition context) { // Record the references in case they are not program types. FieldResolutionResult fieldResolutionResult = appInfo.resolveField(field); fieldResolutionResult.visitFieldResolutionResults( resolutionResult -> { + checkAccess(resolutionResult, context); recordFieldReference( field, resolutionResult.getResolutionPair().asProgramDerivedContext(context)); }, @@ -2355,7 +2374,10 @@ private MethodResolutionResult resolveMethod( MethodResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormatLegacy(method); resolutionResult.visitMethodResolutionResults( - result -> recordMethodReference(method, context), + result -> { + checkAccess(resolutionResult, context); + recordMethodReference(method, context); + }, failedResult -> { markFailedMethodResolutionTargets( method, resolutionResult.asFailedResolution(), context, reason); @@ -2369,18 +2391,15 @@ private MethodResolutionResult resolveMethod( // Record the references in case they are not program types. MethodResolutionResult methodResolutionResult = appInfo.resolveMethodLegacy(method, interfaceInvoke); - methodResolutionResult.forEachMethodResolutionResult( + methodResolutionResult.visitMethodResolutionResults( resolutionResult -> { - if (resolutionResult.isSingleResolution()) { - recordMethodReference( - method, resolutionResult.getResolutionPair().asProgramDerivedContext(context)); - } else { - assert resolutionResult.isFailedResolution(); - markFailedMethodResolutionTargets( - method, resolutionResult.asFailedResolution(), context, reason); - recordMethodReference( - method, context, this::recordFoundClass, this::reportMissingClass); - } + checkAccess(resolutionResult, context); + recordMethodReference( + method, resolutionResult.getResolutionPair().asProgramDerivedContext(context)); + }, + failedResolutionResult -> { + markFailedMethodResolutionTargets(method, failedResolutionResult, context, reason); + recordMethodReference(method, context, this::recordFoundClass, this::reportMissingClass); }); return methodResolutionResult; } @@ -3423,6 +3442,29 @@ private void markVirtualDispatchLambdaTargetAsLive( } } + private void checkAccess(DexClass clazz, ProgramDefinition context) { + if (clazz.isProgramClass() + && AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) { + failedClassResolutionTargets.add(clazz.getType()); + } + } + + private void checkAccess(FieldResolutionResult resolutionResult, ProgramDefinition context) { + if (resolutionResult.getResolvedHolder().isProgramClass() + && resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) { + failedFieldResolutionTargets.add(resolutionResult.getResolvedField().getReference()); + } + checkAccess(resolutionResult.getInitialResolutionHolder(), context); + } + + private void checkAccess(MethodResolutionResult resolutionResult, ProgramDefinition context) { + if (resolutionResult.getResolvedHolder().isProgramClass() + && resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) { + failedMethodResolutionTargets.add(resolutionResult.getResolvedMethod().getReference()); + } + checkAccess(resolutionResult.getInitialResolutionHolder(), context); + } + private void markFailedMethodResolutionTargets( DexMethod symbolicMethod, FailedResolutionResult failedResolution, @@ -4219,6 +4261,7 @@ private EnqueuerResult createEnqueuerResult(AppInfoWithClassHierarchy appInfo, T deadProtoTypes, SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType), Enqueuer.toDescriptorSet(targetedMethods.getItems()), + failedClassResolutionTargets, failedMethodResolutionTargets, failedFieldResolutionTargets, bootstrapMethods, diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java index 1c0d3de9d7..8f08d778e8 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java @@ -91,6 +91,10 @@ public boolean deferTracingOfFieldAccess( return enqueueDeferredEnqueuerActions(field); } + if (resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) { + return enqueueDeferredEnqueuerActions(field); + } + // If the access is from a reachability sensitive method, then bail out. if (context.getHolder().getOrComputeReachabilitySensitive(appView)) { return enqueueDeferredEnqueuerActions(field); diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorCheckCastTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorCheckCastTest.java new file mode 100644 index 0000000000..0b5ff769ab --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorCheckCastTest.java @@ -0,0 +1,83 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.DescriptorUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class PreserveIllegalAccessErrorCheckCastTest extends TestBase { + + private static final String NEW_A_DESCRIPTOR = "LA;"; + + private static List programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws Exception { + programClassFileData = + ImmutableList.of( + transformer(Main.class) + .transformTypeInsnInMethod( + "main", + (opcode, descriptor, visitor) -> { + assertEquals(Opcodes.CHECKCAST, opcode); + visitor.visitTypeInsn( + opcode, DescriptorUtils.getBinaryNameFromDescriptor(NEW_A_DESCRIPTOR)); + }) + .transform(), + transformer(A.class).setClassDescriptor(NEW_A_DESCRIPTOR).transform()); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .allowAccessModification() + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + static class Main { + + public static void main(String[] args) { + Object o = args; + A a = (A) o; + } + } + + static class /*different_package.*/ A {} +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorConstClassTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorConstClassTest.java new file mode 100644 index 0000000000..3c15d17613 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorConstClassTest.java @@ -0,0 +1,85 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.Type; + +@RunWith(Parameterized.class) +public class PreserveIllegalAccessErrorConstClassTest extends TestBase { + + private static final String NEW_A_DESCRIPTOR = "LA;"; + + private static List programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws Exception { + programClassFileData = + ImmutableList.of( + transformer(Main.class) + .transformLdcInsnInMethod( + "main", + (object, visitor) -> { + if (object instanceof Type) { + Type type = (Type) object; + assertEquals(descriptor(A.class), type.getDescriptor()); + visitor.visitLdcInsn(Type.getType(NEW_A_DESCRIPTOR)); + } else { + visitor.visitLdcInsn(object); + } + }) + .transform(), + transformer(A.class).setClassDescriptor(NEW_A_DESCRIPTOR).transform()); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .allowAccessModification() + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + static class Main { + + public static void main(String[] args) { + Class clazz = A.class; + } + } + + static class /*different_package.*/ A {} +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInitialMethodResolutionHolderTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInitialMethodResolutionHolderTest.java new file mode 100644 index 0000000000..bc5ecd18ab --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInitialMethodResolutionHolderTest.java @@ -0,0 +1,85 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRunResult; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PreserveIllegalAccessErrorInitialMethodResolutionHolderTest extends TestBase { + + private static final String NEW_B_DESCRIPTOR = "LB;"; + + private static List programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws Exception { + programClassFileData = + ImmutableList.of( + transformer(Main.class) + .replaceClassDescriptorInMethodInstructions(descriptor(B.class), NEW_B_DESCRIPTOR) + .transform(), + transformer(B.class).setClassDescriptor(NEW_B_DESCRIPTOR).transform()); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(A.class) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .applyIf( + parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V5_1_1), + TestRunResult::assertSuccessWithEmptyOutput, + runResult -> runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class)); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(A.class) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .allowAccessModification() + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), Main.class) + // TODO(b/182129249): Member rebinding does not preserve IAE. + .assertSuccessWithEmptyOutput(); + } + + static class Main { + + public static void main(String[] args) { + B.m(); + } + } + + public static class A { + + public static void m() {} + } + + static class /*different_package.*/ B extends A {} +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInstanceFieldTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInstanceFieldTest.java new file mode 100644 index 0000000000..788eac7094 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInstanceFieldTest.java @@ -0,0 +1,72 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.FieldAccessFlags; +import com.android.tools.r8.transformers.ClassFileTransformer.FieldPredicate; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PreserveIllegalAccessErrorInstanceFieldTest extends TestBase { + + private static byte[] programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws Exception { + programClassFileData = + transformer(A.class) + .setAccessFlags(FieldPredicate.onName("f"), FieldAccessFlags::setPrivate) + .transform(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .allowAccessModification() + .setMinApi(parameters) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + static class Main { + + public static void main(String[] args) { + int x = new A().f; + } + } + + static class A { + + /*private*/ int f; + } +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInstanceOfTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInstanceOfTest.java new file mode 100644 index 0000000000..1b6114fd9b --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorInstanceOfTest.java @@ -0,0 +1,83 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.DescriptorUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class PreserveIllegalAccessErrorInstanceOfTest extends TestBase { + + private static final String NEW_A_DESCRIPTOR = "LA;"; + + private static List programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws Exception { + programClassFileData = + ImmutableList.of( + transformer(Main.class) + .transformTypeInsnInMethod( + "main", + (opcode, descriptor, visitor) -> { + assertEquals(Opcodes.INSTANCEOF, opcode); + visitor.visitTypeInsn( + opcode, DescriptorUtils.getBinaryNameFromDescriptor(NEW_A_DESCRIPTOR)); + }) + .transform(), + transformer(A.class).setClassDescriptor(NEW_A_DESCRIPTOR).transform()); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .allowAccessModification() + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + static class Main { + + public static void main(String[] args) { + Object o = args; + boolean b = o instanceof A; + } + } + + static class /*different_package.*/ A {} +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorMethodTest.java new file mode 100644 index 0000000000..dc19b0ee17 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorMethodTest.java @@ -0,0 +1,72 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.MethodAccessFlags; +import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PreserveIllegalAccessErrorMethodTest extends TestBase { + + private static byte[] programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws Exception { + programClassFileData = + transformer(A.class) + .setAccessFlags(MethodPredicate.onName("foo"), MethodAccessFlags::setPrivate) + .transform(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .allowAccessModification() + .setMinApi(parameters) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + static class Main { + + public static void main(String[] args) { + new A().foo(); + } + } + + static class A { + + /*private*/ void foo() {} + } +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorStaticFieldTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorStaticFieldTest.java new file mode 100644 index 0000000000..ad19dc9d25 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PreserveIllegalAccessErrorStaticFieldTest.java @@ -0,0 +1,72 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.FieldAccessFlags; +import com.android.tools.r8.transformers.ClassFileTransformer.FieldPredicate; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PreserveIllegalAccessErrorStaticFieldTest extends TestBase { + + private static byte[] programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws Exception { + programClassFileData = + transformer(A.class) + .setAccessFlags(FieldPredicate.onName("f"), FieldAccessFlags::setPrivate) + .transform(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .allowAccessModification() + .setMinApi(parameters) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(IllegalAccessError.class); + } + + static class Main { + + public static void main(String[] args) { + int x = A.f; + } + } + + static class A { + + /*private*/ static int f; + } +} diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java index 82ceae16f8..40f0c830d4 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java @@ -79,8 +79,7 @@ public void testR8() throws Exception { .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters) .run(parameters.getRuntime(), Main.class) - // TODO(b/278687711): Access modifier should preserve IllegalAccessErrors. - .assertSuccessWithOutput(EXPECTED_OUTPUT_R8); + .assertSuccessWithOutput(EXPECTED_OUTPUT); } static class Main { From 7f91386c464bdb3681557d8ea545733d137a3f1d Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 20 Jun 2023 14:16:45 +0200 Subject: [PATCH 147/153] Add a @NoAccessModification annotation for testing Change-Id: I8e3df1bedd3819e56abdb317385ec29c34d9ef40 --- .../r8/shaking/KeepConstantArgumentRule.java | 4 +- .../r8/shaking/KeepUnusedArgumentRule.java | 4 +- .../r8/shaking/KeepUnusedReturnValueRule.java | 4 +- ...ule.java => NoAccessModificationRule.java} | 47 +++- .../r8/shaking/NoMethodStaticizingRule.java | 4 +- .../r8/shaking/NoParameterReorderingRule.java | 4 +- .../NoParameterTypeStrengtheningRule.java | 5 +- .../NoRedundantFieldLoadEliminationRule.java | 5 +- .../NoReturnTypeStrengtheningRule.java | 5 +- ...nRule.java => NoValuePropagationRule.java} | 42 +-- .../shaking/ProguardConfigurationParser.java | 257 +++++------------- .../tools/r8/shaking/RootSetUtils.java | 47 ++-- .../tools/r8/NoAccessModification.java | 11 + .../com/android/tools/r8/R8TestBuilder.java | 17 ++ .../android/tools/r8/TestShrinkerBuilder.java | 4 + 15 files changed, 190 insertions(+), 270 deletions(-) rename src/main/java/com/android/tools/r8/shaking/{NoOptimizationBaseRule.java => NoAccessModificationRule.java} (52%) rename src/main/java/com/android/tools/r8/shaking/{MemberValuePropagationRule.java => NoValuePropagationRule.java} (67%) create mode 100644 src/test/java/com/android/tools/r8/NoAccessModification.java diff --git a/src/main/java/com/android/tools/r8/shaking/KeepConstantArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/KeepConstantArgumentRule.java index f234a09029..bc7d2eab73 100644 --- a/src/main/java/com/android/tools/r8/shaking/KeepConstantArgumentRule.java +++ b/src/main/java/com/android/tools/r8/shaking/KeepConstantArgumentRule.java @@ -7,12 +7,12 @@ import com.android.tools.r8.position.Position; import java.util.List; -public class KeepConstantArgumentRule extends NoOptimizationBaseRule { +public class KeepConstantArgumentRule extends ProguardConfigurationRule { public static final String RULE_NAME = "keepconstantarguments"; public static class Builder - extends NoOptimizationBaseRule.Builder { + extends ProguardConfigurationRule.Builder { private Builder() { super(); diff --git a/src/main/java/com/android/tools/r8/shaking/KeepUnusedArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/KeepUnusedArgumentRule.java index 18ba2fff00..06bd39a52b 100644 --- a/src/main/java/com/android/tools/r8/shaking/KeepUnusedArgumentRule.java +++ b/src/main/java/com/android/tools/r8/shaking/KeepUnusedArgumentRule.java @@ -7,12 +7,12 @@ import com.android.tools.r8.position.Position; import java.util.List; -public class KeepUnusedArgumentRule extends NoOptimizationBaseRule { +public class KeepUnusedArgumentRule extends ProguardConfigurationRule { public static final String RULE_NAME = "keepunusedarguments"; public static class Builder - extends NoOptimizationBaseRule.Builder { + extends ProguardConfigurationRule.Builder { private Builder() { super(); diff --git a/src/main/java/com/android/tools/r8/shaking/KeepUnusedReturnValueRule.java b/src/main/java/com/android/tools/r8/shaking/KeepUnusedReturnValueRule.java index 2543285737..0bd866da38 100644 --- a/src/main/java/com/android/tools/r8/shaking/KeepUnusedReturnValueRule.java +++ b/src/main/java/com/android/tools/r8/shaking/KeepUnusedReturnValueRule.java @@ -8,12 +8,12 @@ import com.android.tools.r8.position.Position; import java.util.List; -public class KeepUnusedReturnValueRule extends NoOptimizationBaseRule { +public class KeepUnusedReturnValueRule extends ProguardConfigurationRule { public static final String RULE_NAME = "keepunusedreturnvalue"; public static class Builder - extends NoOptimizationBaseRule.Builder { + extends ProguardConfigurationRule.Builder { Builder() { super(); diff --git a/src/main/java/com/android/tools/r8/shaking/NoOptimizationBaseRule.java b/src/main/java/com/android/tools/r8/shaking/NoAccessModificationRule.java similarity index 52% rename from src/main/java/com/android/tools/r8/shaking/NoOptimizationBaseRule.java rename to src/main/java/com/android/tools/r8/shaking/NoAccessModificationRule.java index a397a7ece4..aeed6eada8 100644 --- a/src/main/java/com/android/tools/r8/shaking/NoOptimizationBaseRule.java +++ b/src/main/java/com/android/tools/r8/shaking/NoAccessModificationRule.java @@ -1,4 +1,4 @@ -// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// Copyright (c) 2023, 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. @@ -8,18 +8,42 @@ import com.android.tools.r8.position.Position; import java.util.List; -public abstract class NoOptimizationBaseRule> - extends ProguardConfigurationRule { +public class NoAccessModificationRule extends ProguardConfigurationRule { - public abstract static class Builder, B extends Builder> - extends ProguardConfigurationRule.Builder { + public static final String RULE_NAME = "noaccessmodification"; - Builder() { + public static class Builder + extends ProguardConfigurationRule.Builder { + + private Builder() { super(); } + + @Override + public NoAccessModificationRule.Builder self() { + return this; + } + + @Override + public NoAccessModificationRule build() { + return new NoAccessModificationRule( + origin, + getPosition(), + source, + buildClassAnnotations(), + classAccessFlags, + negatedClassAccessFlags, + classTypeNegated, + classType, + classNames, + buildInheritanceAnnotations(), + inheritanceClassName, + inheritanceIsExtends, + memberRules); + } } - NoOptimizationBaseRule( + private NoAccessModificationRule( Origin origin, Position position, String source, @@ -48,4 +72,13 @@ public abstract static class Builder, B exte inheritanceIsExtends, memberRules); } + + public static Builder builder() { + return new Builder(); + } + + @Override + String typeString() { + return RULE_NAME; + } } diff --git a/src/main/java/com/android/tools/r8/shaking/NoMethodStaticizingRule.java b/src/main/java/com/android/tools/r8/shaking/NoMethodStaticizingRule.java index 84fd67d3bb..54c514bedc 100644 --- a/src/main/java/com/android/tools/r8/shaking/NoMethodStaticizingRule.java +++ b/src/main/java/com/android/tools/r8/shaking/NoMethodStaticizingRule.java @@ -8,12 +8,12 @@ import com.android.tools.r8.position.Position; import java.util.List; -public class NoMethodStaticizingRule extends NoOptimizationBaseRule { +public class NoMethodStaticizingRule extends ProguardConfigurationRule { public static final String RULE_NAME = "nomethodstaticizing"; public static class Builder - extends NoOptimizationBaseRule.Builder { + extends ProguardConfigurationRule.Builder { Builder() { super(); diff --git a/src/main/java/com/android/tools/r8/shaking/NoParameterReorderingRule.java b/src/main/java/com/android/tools/r8/shaking/NoParameterReorderingRule.java index c8d0e6c115..901b3bcde5 100644 --- a/src/main/java/com/android/tools/r8/shaking/NoParameterReorderingRule.java +++ b/src/main/java/com/android/tools/r8/shaking/NoParameterReorderingRule.java @@ -8,12 +8,12 @@ import com.android.tools.r8.position.Position; import java.util.List; -public class NoParameterReorderingRule extends NoOptimizationBaseRule { +public class NoParameterReorderingRule extends ProguardConfigurationRule { public static final String RULE_NAME = "noparameterreordering"; public static class Builder - extends NoOptimizationBaseRule.Builder { + extends ProguardConfigurationRule.Builder { Builder() { super(); diff --git a/src/main/java/com/android/tools/r8/shaking/NoParameterTypeStrengtheningRule.java b/src/main/java/com/android/tools/r8/shaking/NoParameterTypeStrengtheningRule.java index e0b6594039..172793c52d 100644 --- a/src/main/java/com/android/tools/r8/shaking/NoParameterTypeStrengtheningRule.java +++ b/src/main/java/com/android/tools/r8/shaking/NoParameterTypeStrengtheningRule.java @@ -8,13 +8,12 @@ import com.android.tools.r8.position.Position; import java.util.List; -public class NoParameterTypeStrengtheningRule - extends NoOptimizationBaseRule { +public class NoParameterTypeStrengtheningRule extends ProguardConfigurationRule { public static final String RULE_NAME = "noparametertypestrengthening"; public static class Builder - extends NoOptimizationBaseRule.Builder { + extends ProguardConfigurationRule.Builder { Builder() { super(); diff --git a/src/main/java/com/android/tools/r8/shaking/NoRedundantFieldLoadEliminationRule.java b/src/main/java/com/android/tools/r8/shaking/NoRedundantFieldLoadEliminationRule.java index 87c5ee081f..3921ff1fd9 100644 --- a/src/main/java/com/android/tools/r8/shaking/NoRedundantFieldLoadEliminationRule.java +++ b/src/main/java/com/android/tools/r8/shaking/NoRedundantFieldLoadEliminationRule.java @@ -8,13 +8,12 @@ import com.android.tools.r8.position.Position; import java.util.List; -public class NoRedundantFieldLoadEliminationRule - extends NoOptimizationBaseRule { +public class NoRedundantFieldLoadEliminationRule extends ProguardConfigurationRule { public static final String RULE_NAME = "noredundantfieldloadelimination"; public static class Builder - extends NoOptimizationBaseRule.Builder { + extends ProguardConfigurationRule.Builder { Builder() { super(); diff --git a/src/main/java/com/android/tools/r8/shaking/NoReturnTypeStrengtheningRule.java b/src/main/java/com/android/tools/r8/shaking/NoReturnTypeStrengtheningRule.java index 21c637187b..11de40282b 100644 --- a/src/main/java/com/android/tools/r8/shaking/NoReturnTypeStrengtheningRule.java +++ b/src/main/java/com/android/tools/r8/shaking/NoReturnTypeStrengtheningRule.java @@ -8,13 +8,12 @@ import com.android.tools.r8.position.Position; import java.util.List; -public class NoReturnTypeStrengtheningRule - extends NoOptimizationBaseRule { +public class NoReturnTypeStrengtheningRule extends ProguardConfigurationRule { public static final String RULE_NAME = "noreturntypestrengthening"; public static class Builder - extends NoOptimizationBaseRule.Builder { + extends ProguardConfigurationRule.Builder { Builder() { super(); diff --git a/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java b/src/main/java/com/android/tools/r8/shaking/NoValuePropagationRule.java similarity index 67% rename from src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java rename to src/main/java/com/android/tools/r8/shaking/NoValuePropagationRule.java index c26832bc96..b1ac352585 100644 --- a/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java +++ b/src/main/java/com/android/tools/r8/shaking/NoValuePropagationRule.java @@ -3,39 +3,29 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.shaking; -import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; import java.util.List; -public class MemberValuePropagationRule extends ProguardConfigurationRule { +public class NoValuePropagationRule extends ProguardConfigurationRule { - public enum Type { - NEVER - } + public static final String RULE_NAME = "neverpropagatevalue"; public static class Builder - extends ProguardConfigurationRule.Builder { + extends ProguardConfigurationRule.Builder { private Builder() { super(); } - Type type; - @Override public Builder self() { return this; } - public Builder setType(Type type) { - this.type = type; - return this; - } - @Override - public MemberValuePropagationRule build() { - return new MemberValuePropagationRule( + public NoValuePropagationRule build() { + return new NoValuePropagationRule( origin, getPosition(), source, @@ -48,14 +38,11 @@ public MemberValuePropagationRule build() { buildInheritanceAnnotations(), inheritanceClassName, inheritanceIsExtends, - memberRules, - type); + memberRules); } } - private final Type type; - - private MemberValuePropagationRule( + private NoValuePropagationRule( Origin origin, Position position, String source, @@ -68,8 +55,7 @@ private MemberValuePropagationRule( List inheritanceAnnotations, ProguardTypeMatcher inheritanceClassName, boolean inheritanceIsExtends, - List memberRules, - Type type) { + List memberRules) { super( origin, position, @@ -84,24 +70,14 @@ private MemberValuePropagationRule( inheritanceClassName, inheritanceIsExtends, memberRules); - this.type = type; } public static Builder builder() { return new Builder(); } - public Type getType() { - return type; - } - @Override String typeString() { - switch (type) { - case NEVER: - return "neverpropagatevalue"; - } - throw new Unreachable("Unknown member value propagation type " + type); + return RULE_NAME; } - } diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java index 2c51922626..bec7ae605a 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java @@ -320,11 +320,12 @@ private boolean parseOption() throws ProguardRuleParserException { } else if (acceptString("keepparameternames")) { configurationBuilder.setKeepParameterNames(true, origin, getPosition(optionStart)); } else if (acceptString("checkdiscard")) { - ProguardCheckDiscardRule rule = parseCheckDiscardRule(optionStart); + ProguardCheckDiscardRule rule = + parseRuleWithClassSpec(optionStart, ProguardCheckDiscardRule.builder()); configurationBuilder.addRule(rule); } else if (acceptString("checkenumstringsdiscarded")) { // Not supported, ignore. - parseCheckDiscardRule(optionStart); + parseRuleWithClassSpec(optionStart, ProguardCheckDiscardRule.builder()); } else if (acceptString("keepdirectories")) { configurationBuilder.enableKeepDirectories(); parsePathFilter(configurationBuilder::addKeepDirectories); @@ -332,7 +333,8 @@ private boolean parseOption() throws ProguardRuleParserException { ProguardKeepRule rule = parseKeepRule(optionStart); configurationBuilder.addRule(rule); } else if (acceptString("whyareyoukeeping")) { - ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule(optionStart); + ProguardWhyAreYouKeepingRule rule = + parseRuleWithClassSpec(optionStart, ProguardWhyAreYouKeepingRule.builder()); configurationBuilder.addRule(rule); } else if (acceptString("dontoptimize")) { configurationBuilder.disableOptimization(); @@ -458,7 +460,9 @@ private boolean parseOption() throws ProguardRuleParserException { parseFileInputDependency( inputDependencyConsumer::acceptProguardPackageObfuscationDictionary)); } else if (acceptString("alwaysinline")) { - InlineRule rule = parseInlineRule(InlineRule.Type.ALWAYS, optionStart); + InlineRule rule = + parseRuleWithClassSpec( + optionStart, InlineRule.builder().setType(InlineRule.Type.ALWAYS)); configurationBuilder.addRule(rule); } else if (acceptString("adaptclassstrings")) { parseClassFilter(configurationBuilder::addAdaptClassStringsPattern); @@ -467,7 +471,8 @@ private boolean parseOption() throws ProguardRuleParserException { } else if (acceptString("adaptresourcefilecontents")) { parsePathFilter(configurationBuilder::addAdaptResourceFileContents); } else if (acceptString("identifiernamestring")) { - configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart)); + configurationBuilder.addRule( + parseRuleWithClassSpec(optionStart, ProguardIdentifierNameStringRule.builder())); } else if (acceptString("if")) { configurationBuilder.addRule(parseIfRule(optionStart)); } else if (acceptString("addconfigurationdebugging")) { @@ -507,7 +512,8 @@ private boolean parseExperimentalOption(TextPosition optionStart) } if (options.isExperimentalWhyAreYouNotInliningEnabled()) { if (acceptString(WhyAreYouNotInliningRule.RULE_NAME)) { - configurationBuilder.addRule(parseWhyAreYouNotInliningRule(optionStart)); + configurationBuilder.addRule( + parseRuleWithClassSpec(optionStart, WhyAreYouNotInliningRule.builder())); return true; } } @@ -525,118 +531,144 @@ private boolean parseTestingOption(TextPosition optionStart) } if (acceptString(KeepConstantArgumentRule.RULE_NAME)) { KeepConstantArgumentRule rule = - parseNoOptimizationRule(optionStart, KeepConstantArgumentRule.builder()); + parseRuleWithClassSpec(optionStart, KeepConstantArgumentRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(KeepUnusedArgumentRule.RULE_NAME)) { KeepUnusedArgumentRule rule = - parseNoOptimizationRule(optionStart, KeepUnusedArgumentRule.builder()); + parseRuleWithClassSpec(optionStart, KeepUnusedArgumentRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(KeepUnusedReturnValueRule.RULE_NAME)) { KeepUnusedReturnValueRule rule = - parseNoOptimizationRule(optionStart, KeepUnusedReturnValueRule.builder()); + parseRuleWithClassSpec(optionStart, KeepUnusedReturnValueRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString("alwaysclassinline")) { - ClassInlineRule rule = parseClassInlineRule(ClassInlineRule.Type.ALWAYS, optionStart); + ClassInlineRule rule = + parseRuleWithClassSpec( + optionStart, ClassInlineRule.builder().setType(ClassInlineRule.Type.ALWAYS)); configurationBuilder.addRule(rule); return true; } if (acceptString("neverclassinline")) { - ClassInlineRule rule = parseClassInlineRule(ClassInlineRule.Type.NEVER, optionStart); + ClassInlineRule rule = + parseRuleWithClassSpec( + optionStart, ClassInlineRule.builder().setType(ClassInlineRule.Type.NEVER)); configurationBuilder.addRule(rule); return true; } if (acceptString("neverinline")) { - InlineRule rule = parseInlineRule(InlineRule.Type.NEVER, optionStart); + InlineRule rule = + parseRuleWithClassSpec( + optionStart, InlineRule.builder().setType(InlineRule.Type.NEVER)); configurationBuilder.addRule(rule); return true; } if (acceptString("neversinglecallerinline")) { - InlineRule rule = parseInlineRule(InlineRule.Type.NEVER_SINGLE_CALLER, optionStart); + InlineRule rule = + parseRuleWithClassSpec( + optionStart, InlineRule.builder().setType(InlineRule.Type.NEVER_SINGLE_CALLER)); + configurationBuilder.addRule(rule); + return true; + } + if (acceptString(NoAccessModificationRule.RULE_NAME)) { + NoAccessModificationRule rule = + parseRuleWithClassSpec(optionStart, NoAccessModificationRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(NoFieldTypeStrengtheningRule.RULE_NAME)) { - ProguardConfigurationRule rule = parseNoFieldTypeStrengtheningRule(optionStart); + NoFieldTypeStrengtheningRule rule = + parseRuleWithClassSpec(optionStart, NoFieldTypeStrengtheningRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(NoUnusedInterfaceRemovalRule.RULE_NAME)) { - ProguardConfigurationRule rule = parseNoUnusedInterfaceRemovalRule(optionStart); + NoUnusedInterfaceRemovalRule rule = + parseRuleWithClassSpec(optionStart, NoUnusedInterfaceRemovalRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(NoVerticalClassMergingRule.RULE_NAME)) { - ProguardConfigurationRule rule = parseNoVerticalClassMergingRule(optionStart); + NoVerticalClassMergingRule rule = + parseRuleWithClassSpec(optionStart, NoVerticalClassMergingRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(NoHorizontalClassMergingRule.RULE_NAME)) { - ProguardConfigurationRule rule = parseNoHorizontalClassMergingRule(optionStart); + NoHorizontalClassMergingRule rule = + parseRuleWithClassSpec(optionStart, NoHorizontalClassMergingRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(NoMethodStaticizingRule.RULE_NAME)) { - ProguardConfigurationRule rule = - parseNoOptimizationRule(optionStart, NoMethodStaticizingRule.builder()); + NoMethodStaticizingRule rule = + parseRuleWithClassSpec(optionStart, NoMethodStaticizingRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(NoParameterReorderingRule.RULE_NAME)) { - ProguardConfigurationRule rule = - parseNoOptimizationRule(optionStart, NoParameterReorderingRule.builder()); + NoParameterReorderingRule rule = + parseRuleWithClassSpec(optionStart, NoParameterReorderingRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(NoParameterTypeStrengtheningRule.RULE_NAME)) { - ProguardConfigurationRule rule = - parseNoOptimizationRule(optionStart, NoParameterTypeStrengtheningRule.builder()); + NoParameterTypeStrengtheningRule rule = + parseRuleWithClassSpec(optionStart, NoParameterTypeStrengtheningRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(NoRedundantFieldLoadEliminationRule.RULE_NAME)) { - ProguardConfigurationRule rule = - parseNoOptimizationRule(optionStart, NoRedundantFieldLoadEliminationRule.builder()); + NoRedundantFieldLoadEliminationRule rule = + parseRuleWithClassSpec(optionStart, NoRedundantFieldLoadEliminationRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString(NoReturnTypeStrengtheningRule.RULE_NAME)) { - ProguardConfigurationRule rule = - parseNoOptimizationRule(optionStart, NoReturnTypeStrengtheningRule.builder()); + NoReturnTypeStrengtheningRule rule = + parseRuleWithClassSpec(optionStart, NoReturnTypeStrengtheningRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString("neverpropagatevalue")) { - MemberValuePropagationRule rule = - parseMemberValuePropagationRule(MemberValuePropagationRule.Type.NEVER, optionStart); + NoValuePropagationRule rule = + parseRuleWithClassSpec(optionStart, NoValuePropagationRule.builder()); configurationBuilder.addRule(rule); return true; } if (acceptString("neverreprocessclassinitializer")) { configurationBuilder.addRule( - parseReprocessClassInitializerRule( - ReprocessClassInitializerRule.Type.NEVER, optionStart)); + parseRuleWithClassSpec( + optionStart, + ReprocessClassInitializerRule.builder() + .setType(ReprocessClassInitializerRule.Type.NEVER))); return true; } if (acceptString("neverreprocessmethod")) { configurationBuilder.addRule( - parseReprocessMethodRule(ReprocessMethodRule.Type.NEVER, optionStart)); + parseRuleWithClassSpec( + optionStart, + ReprocessMethodRule.builder().setType(ReprocessMethodRule.Type.NEVER))); return true; } if (acceptString("reprocessclassinitializer")) { configurationBuilder.addRule( - parseReprocessClassInitializerRule( - ReprocessClassInitializerRule.Type.ALWAYS, optionStart)); + parseRuleWithClassSpec( + optionStart, + ReprocessClassInitializerRule.builder() + .setType(ReprocessClassInitializerRule.Type.ALWAYS))); return true; } if (acceptString("reprocessmethod")) { configurationBuilder.addRule( - parseReprocessMethodRule(ReprocessMethodRule.Type.ALWAYS, optionStart)); + parseRuleWithClassSpec( + optionStart, + ReprocessMethodRule.builder().setType(ReprocessMethodRule.Type.ALWAYS))); return true; } } @@ -826,87 +858,8 @@ private ProguardKeepRule parseKeepRule(Position start) return keepRuleBuilder.build(); } - private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule(Position start) - throws ProguardRuleParserException { - ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder() - .setOrigin(origin) - .setStart(start); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - - private ProguardCheckDiscardRule parseCheckDiscardRule(Position start) - throws ProguardRuleParserException { - ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder() - .setOrigin(origin) - .setStart(start); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - - private ClassInlineRule parseClassInlineRule(ClassInlineRule.Type type, Position start) - throws ProguardRuleParserException { - ClassInlineRule.Builder keepRuleBuilder = - ClassInlineRule.builder().setOrigin(origin).setStart(start).setType(type); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - - private NoFieldTypeStrengtheningRule parseNoFieldTypeStrengtheningRule(Position start) - throws ProguardRuleParserException { - NoFieldTypeStrengtheningRule.Builder keepRuleBuilder = - NoFieldTypeStrengtheningRule.builder().setOrigin(origin).setStart(start); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - - private NoUnusedInterfaceRemovalRule parseNoUnusedInterfaceRemovalRule(Position start) - throws ProguardRuleParserException { - NoUnusedInterfaceRemovalRule.Builder keepRuleBuilder = - NoUnusedInterfaceRemovalRule.builder().setOrigin(origin).setStart(start); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - - private NoVerticalClassMergingRule parseNoVerticalClassMergingRule(Position start) - throws ProguardRuleParserException { - NoVerticalClassMergingRule.Builder keepRuleBuilder = - NoVerticalClassMergingRule.builder().setOrigin(origin).setStart(start); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - - private NoHorizontalClassMergingRule parseNoHorizontalClassMergingRule(Position start) - throws ProguardRuleParserException { - NoHorizontalClassMergingRule.Builder keepRuleBuilder = - NoHorizontalClassMergingRule.builder().setOrigin(origin).setStart(start); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - - private , B extends NoOptimizationBaseRule.Builder> - R parseNoOptimizationRule(Position start, B builder) throws ProguardRuleParserException { + private > + R parseRuleWithClassSpec(Position start, B builder) throws ProguardRuleParserException { builder.setOrigin(origin).setStart(start); parseClassSpec(builder); Position end = getPosition(); @@ -915,44 +868,6 @@ R parseNoOptimizationRule(Position start, B builder) throws ProguardRuleParserEx return builder.build(); } - private MemberValuePropagationRule parseMemberValuePropagationRule( - MemberValuePropagationRule.Type type, Position start) - throws ProguardRuleParserException { - MemberValuePropagationRule .Builder keepRuleBuilder = - MemberValuePropagationRule.builder().setOrigin(origin).setStart(start).setType(type); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - - private InlineRule parseInlineRule(InlineRule.Type type, Position start) - throws ProguardRuleParserException { - InlineRule.Builder keepRuleBuilder = InlineRule.builder() - .setOrigin(origin) - .setStart(start) - .setType(type); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - - private ProguardIdentifierNameStringRule parseIdentifierNameStringRule(Position start) - throws ProguardRuleParserException { - ProguardIdentifierNameStringRule.Builder keepRuleBuilder = - ProguardIdentifierNameStringRule.builder() - .setOrigin(origin) - .setStart(start); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - private ProguardIfRule parseIfRule(TextPosition optionStart) throws ProguardRuleParserException { ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder() @@ -1018,40 +933,6 @@ private boolean parseMaximumRemovedAndroidLogLevelRule(Position start) return false; } - private ReprocessClassInitializerRule parseReprocessClassInitializerRule( - ReprocessClassInitializerRule.Type type, Position start) - throws ProguardRuleParserException { - ReprocessClassInitializerRule.Builder builder = - ReprocessClassInitializerRule.builder().setOrigin(origin).setStart(start).setType(type); - parseClassSpec(builder); - Position end = getPosition(); - builder.setSource(getSourceSnippet(contents, start, end)); - builder.setEnd(end); - return builder.build(); - } - - private ReprocessMethodRule parseReprocessMethodRule( - ReprocessMethodRule.Type type, Position start) throws ProguardRuleParserException { - ReprocessMethodRule.Builder builder = - ReprocessMethodRule.builder().setOrigin(origin).setStart(start).setType(type); - parseClassSpec(builder); - Position end = getPosition(); - builder.setSource(getSourceSnippet(contents, start, end)); - builder.setEnd(end); - return builder.build(); - } - - private WhyAreYouNotInliningRule parseWhyAreYouNotInliningRule(Position start) - throws ProguardRuleParserException { - WhyAreYouNotInliningRule.Builder keepRuleBuilder = - WhyAreYouNotInliningRule.builder().setOrigin(origin).setStart(start); - parseClassSpec(keepRuleBuilder); - Position end = getPosition(); - keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)); - keepRuleBuilder.setEnd(end); - return keepRuleBuilder.build(); - } - void verifyAndLinkBackReferences(Iterable wildcards) { List patterns = new ArrayList<>(); boolean backReferenceStarted = false; diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java index 192de9222c..367a2fc2a7 100644 --- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java +++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java @@ -273,7 +273,8 @@ private void process(DexClass clazz, ProguardConfigurationRule rule, ProguardIfR evaluateCheckDiscardRule(clazz, rule.asProguardCheckDiscardRule()); } else if (rule instanceof CheckEnumUnboxedRule) { evaluateCheckEnumUnboxedRule(clazz, (CheckEnumUnboxedRule) rule); - } else if (rule instanceof ProguardWhyAreYouKeepingRule) { + } else if (rule instanceof NoAccessModificationRule + || rule instanceof ProguardWhyAreYouKeepingRule) { markClass(clazz, rule, ifRule); markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule); markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule); @@ -311,7 +312,7 @@ private void process(DexClass clazz, ProguardConfigurationRule rule, ProguardIfR if (allRulesSatisfied(memberKeepRules, clazz)) { markClass(clazz, rule, ifRule); } - } else if (rule instanceof MemberValuePropagationRule) { + } else if (rule instanceof NoValuePropagationRule) { markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule); markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule); } else if (rule instanceof ProguardIdentifierNameStringRule) { @@ -1231,6 +1232,12 @@ private synchronized void addItemToSets( throw new Unreachable(); } context.markAsUsed(); + } else if (context instanceof NoAccessModificationRule) { + assert item.isProgramDefinition(); + dependentMinimumKeepInfo + .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference()) + .disallowAccessModification(); + context.markAsUsed(); } else if (context instanceof NoFieldTypeStrengtheningRule) { assert item.isProgramField(); dependentMinimumKeepInfo @@ -1282,27 +1289,21 @@ private synchronized void addItemToSets( .asMethodJoiner() .disallowReturnTypeStrengthening(); context.markAsUsed(); - } else if (context instanceof MemberValuePropagationRule) { - switch (((MemberValuePropagationRule) context).getType()) { - case NEVER: - // Only add members from propgram classes to `neverPropagateValue` since class member - // values from library types are not propagated by default. - if (item.isField()) { - DexClassAndField field = item.asField(); - if (field.isProgramField()) { - neverPropagateValue.add(field.getReference()); - context.markAsUsed(); - } - } else if (item.isMethod()) { - DexClassAndMethod method = item.asMethod(); - if (method.isProgramMethod()) { - neverPropagateValue.add(method.getReference()); - context.markAsUsed(); - } - } - break; - default: - throw new Unreachable(); + } else if (context instanceof NoValuePropagationRule) { + // Only add members from propgram classes to `neverPropagateValue` since class member values + // from library types are not propagated by default. + if (item.isField()) { + DexClassAndField field = item.asField(); + if (field.isProgramField()) { + neverPropagateValue.add(field.getReference()); + context.markAsUsed(); + } + } else if (item.isMethod()) { + DexClassAndMethod method = item.asMethod(); + if (method.isProgramMethod()) { + neverPropagateValue.add(method.getReference()); + context.markAsUsed(); + } } } else if (context instanceof ProguardIdentifierNameStringRule) { evaluateIdentifierNameStringRule(item, context, ifRule); diff --git a/src/test/java/com/android/tools/r8/NoAccessModification.java b/src/test/java/com/android/tools/r8/NoAccessModification.java new file mode 100644 index 0000000000..4b6fdf38f1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/NoAccessModification.java @@ -0,0 +1,11 @@ +// Copyright (c) 2023, 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface NoAccessModification {} diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java index c6f1d08625..664ce2ece4 100644 --- a/src/test/java/com/android/tools/r8/R8TestBuilder.java +++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java @@ -23,6 +23,7 @@ import com.android.tools.r8.shaking.CheckEnumUnboxedRule; import com.android.tools.r8.shaking.CollectingGraphConsumer; import com.android.tools.r8.shaking.KeepUnusedReturnValueRule; +import com.android.tools.r8.shaking.NoAccessModificationRule; import com.android.tools.r8.shaking.NoFieldTypeStrengtheningRule; import com.android.tools.r8.shaking.NoHorizontalClassMergingRule; import com.android.tools.r8.shaking.NoMethodStaticizingRule; @@ -484,6 +485,10 @@ T addInternalMatchAnnotationOnFieldRule(String name, Class "-" + name + " class * { @" + annotation.getTypeName() + " ; }"); } + T addInternalMatchAnnotationOnMemberRule(String name, Class annotation) { + return addInternalKeepRules("-" + name + " class * { @" + annotation.getTypeName() + " *; }"); + } + T addInternalMatchAnnotationOnMethodRule(String name, Class annotation) { return addInternalKeepRules( "-" + name + " class * { @" + annotation.getTypeName() + " ; }"); @@ -550,6 +555,18 @@ public T enableKeepUnusedReturnValueAnnotations() { KeepUnusedReturnValueRule.RULE_NAME, KeepUnusedReturnValue.class); } + public T enableNoAccessModificationAnnotationsForClasses() { + return addNoAccessModificationAnnotation() + .addInternalMatchInterfaceRule( + NoAccessModificationRule.RULE_NAME, NoAccessModification.class); + } + + public T enableNoAccessModificationAnnotationsForMembers() { + return addNoAccessModificationAnnotation() + .addInternalMatchAnnotationOnMemberRule( + NoAccessModificationRule.RULE_NAME, NoAccessModification.class); + } + public T enableNoFieldTypeStrengtheningAnnotations() { return addNoFieldTypeStrengtheningAnnotation() .addInternalMatchAnnotationOnFieldRule( diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java index d9e3f9114a..cfd2a95a28 100644 --- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java +++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java @@ -512,6 +512,10 @@ public final T addNeverSingleCallerInlineAnnotations() { return addTestingAnnotation(NeverSingleCallerInline.class); } + public final T addNoAccessModificationAnnotation() { + return addTestingAnnotation(NoAccessModification.class); + } + public final T addNoFieldTypeStrengtheningAnnotation() { return addTestingAnnotation(NoFieldTypeStrengthening.class); } From 3d5228d88cec067d98dfb52fdc6d4c31197148bc Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Tue, 20 Jun 2023 16:30:07 +0200 Subject: [PATCH 148/153] [LIR] Adjust tests after changing LIR simple inlining limit. Bug: b/225838009 Change-Id: I4fb971de1fd97edecb22c7c413befc5bafc05c22 --- .../tools/r8/desugar/desugaredlibrary/JavaTimeTest.java | 6 ++++++ .../tools/r8/enumunboxing/EnumUnboxNullArgumentTest.java | 3 ++- .../tools/r8/ir/optimize/inliner/InliningOptimize.java | 5 +++++ .../r8/shaking/annotations/b137392797/B137392797.java | 7 +++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java index d6ede8b655..09d32131e7 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java @@ -86,6 +86,12 @@ public void testTime() throws Throwable { .addKeepMainRule(TestClass.class) .enableNoVerticalClassMergingAnnotations() .enableInliningAnnotations() + .addOptionsModification( + options -> { + // The check for $default$query relies on inlining. + options.testing.enableLir(); + options.inlinerOptions().simpleInliningInstructionLimit = 5; + }) .compile() .inspect(i -> checkRewrittenInvokes(i, compilationSpecification.isProgramShrink())) .run(parameters.getRuntime(), TestClass.class) diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNullArgumentTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNullArgumentTest.java index 7c5dfb8826..c55ecf67ab 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNullArgumentTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNullArgumentTest.java @@ -38,7 +38,8 @@ public void testR8() { .addInnerClasses(getClass()) .setMinApi(parameters) .addKeepMainRule(Main.class) - .enableInliningAnnotations() + // TODO(b/287193321): Using LIR avoids the issue, is it fixed or just hidden? + .addOptionsModification(options -> options.testing.disableLir()) .compileWithExpectedDiagnostics( diagnostics -> { if (parameters.isDexRuntime()) { diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningOptimize.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningOptimize.java index 0c0bac6731..dd9edd7c14 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningOptimize.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningOptimize.java @@ -28,6 +28,11 @@ public void test() throws Exception { testForR8(Backend.DEX) .addProgramClasses(Bar.class, Foobar.class) .addKeepRules("-keep,allowoptimization class ** {\n" + "*;\n" + "}") + .addOptionsModification( + options -> { + options.testing.enableLir(); + options.inlinerOptions().simpleInliningInstructionLimit = 5; + }) .compile() .inspect( inspector -> { diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java index f98c16a5eb..df9580ced3 100644 --- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java +++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java @@ -84,6 +84,13 @@ public void testR8() throws Exception { " static com.squareup.wire.WireField$Label OPTIONAL;", "}")) .setMinApi(parameters) + .addOptionsModification( + options -> { + // The default limit for LIR is 2 at time of writing. + // The constructor inlining check needs a limit of 4 to trigger. + options.testing.enableLir(); + options.inlinerOptions().simpleInliningInstructionLimit = 4; + }) .compile() .inspect(this::checkEnumUses) .run(parameters.getRuntime(), TestClass.class, "com.squareup.demo.myapplication.Test") From c5d7743e1ecd293e317b6ec7a335e33a38a1b88c Mon Sep 17 00:00:00 2001 From: Ian Zerny Date: Tue, 20 Jun 2023 16:30:40 +0200 Subject: [PATCH 149/153] [LIR] Default enable the use of light-weight IR. Bug: b/225838009 Change-Id: I804ff8c6dd0e1785f6dcf56529cf8240a94e5212 --- src/main/java/com/android/tools/r8/utils/InternalOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3e1101dd91..bcc824ddd5 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -2086,7 +2086,7 @@ public boolean shouldApplyInliningToInlinee( public static class TestingOptions { public boolean roundtripThroughLir = false; - private boolean useLir = System.getProperty("com.android.tools.r8.uselir") != null; + private boolean useLir = System.getProperty("com.android.tools.r8.nolir") == null; public void enableLir() { useLir = true; From 78140ba4748ce5634927a11cbed12e2994e8fea0 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 20 Jun 2023 14:49:46 +0200 Subject: [PATCH 150/153] Avoid tracing of $deserializeLambda$ methods Change-Id: Ibd43e7b9967e25e820dac00c6fee94d9560413b7 --- .../tools/r8/ir/conversion/PrimaryD8L8IRConverter.java | 3 +-- .../r8/ir/desugar/CfInstructionDesugaringEventConsumer.java | 1 + .../desugar/lambda/LambdaDeserializationMethodRemover.java | 6 +++++- src/main/java/com/android/tools/r8/shaking/Enqueuer.java | 5 +++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java index 172e90aa8e..bcbbe636eb 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java @@ -38,7 +38,6 @@ import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; -import java.io.IOException; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -53,7 +52,7 @@ public PrimaryD8L8IRConverter(AppView appView, Timing timing) { } public void convert(AppView appView, ExecutorService executorService) - throws ExecutionException, IOException { + throws ExecutionException { LambdaDeserializationMethodRemover.run(appView); workaroundAbstractMethodOnNonAbstractClassVerificationBug(executorService); DexApplication application = appView.appInfo().app(); diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java index 01a25e7c78..7e03d91002 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java @@ -742,6 +742,7 @@ private void finalizeLambdaDesugaring() { }); // Remove all '$deserializeLambda$' methods which are not supported by desugaring. + assert appView.options().isGeneratingDex() || classesWithSerializableLambdas.isEmpty(); LambdaDeserializationMethodRemover.run(appView, classesWithSerializableLambdas); } diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDeserializationMethodRemover.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDeserializationMethodRemover.java index d496b33b6d..b96948cd29 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDeserializationMethodRemover.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDeserializationMethodRemover.java @@ -6,6 +6,7 @@ import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import java.util.Collection; @@ -24,7 +25,10 @@ public static void run(AppView appView, Collection classes) assert appView.options().desugarState.isOn() || classes.isEmpty(); DexMethod reference = appView.dexItemFactory().deserializeLambdaMethod; for (DexProgramClass clazz : classes) { - clazz.removeMethod(reference); + DexEncodedMethod method = clazz.lookupMethod(reference); + if (method != null && method.getAccessFlags().isPrivate()) { + clazz.removeMethod(reference); + } } } } diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index de7943fbb2..471c6b0748 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java @@ -3181,6 +3181,11 @@ private void traceFieldReference( } private void markDirectStaticOrConstructorMethodAsLive(ProgramMethod method, KeepReason reason) { + if (appView.options().isGeneratingDex() + && method.getReference().match(appView.dexItemFactory().deserializeLambdaMethod) + && method.getAccessFlags().isPrivate()) { + return; + } if (workList.enqueueMarkMethodLiveAction(method, method, reason)) { assert workList.enqueueAssertAction( () -> { From 5c034d3629c18a047ed8c42611a9a8f313858578 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 20 Jun 2023 18:14:49 +0200 Subject: [PATCH 151/153] Update expected error in IAE test Change-Id: Ieb60f8441a9bbabc206fd88fe78eece3f1231cb6 --- .../PackagePrivateInitialResolutionHolderTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateInitialResolutionHolderTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateInitialResolutionHolderTest.java index 68bcc86210..021f56efb1 100644 --- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateInitialResolutionHolderTest.java +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateInitialResolutionHolderTest.java @@ -95,10 +95,9 @@ public void testR8() throws Exception { .addProgramClassFileData(getRewrittenResources()) .addKeepMainRule(Main.class) .setMinApi(parameters) - .compile() .run(parameters.getRuntime(), Main.class) - // TODO(b/264522833): Should be IllegalAccessError. - .assertFailureWithErrorThatThrows(AbstractMethodError.class); + // TODO(b/264522833): Should be IllegalAccessError, but member rebinding "fixes" the code. + .assertFailureWithErrorThatThrows(NullPointerException.class); } private Collection getRewrittenResources() throws Exception { From 71830936a1cc89ddff436523de4dbad652a18791 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 20 Jun 2023 19:53:43 +0200 Subject: [PATCH 152/153] Disable lir when proto shrinking is enabled Change-Id: I2293711f12481967d64050cb8063157f6ab50b89 --- src/main/java/com/android/tools/r8/utils/InternalOptions.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 bcc824ddd5..6ef169126a 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -2097,7 +2097,9 @@ public void disableLir() { } public boolean canUseLir(AppView appView) { - return useLir && appView.enableWholeProgramOptimizations(); + return useLir + && appView.enableWholeProgramOptimizations() + && !appView.options().protoShrinking().isProtoShrinkingEnabled(); } // If false, use the desugared library implementation when desugared library is enabled. From 1ff9bbe2e1c61518b797bcc16173bbb64921b543 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Tue, 20 Jun 2023 22:06:50 +0200 Subject: [PATCH 153/153] Fix NPE in GeneratedMessageLiteBuilderShrinker Change-Id: I1bbc8ff52d5660c6bdeb09f41516c114c8368b85 --- .../proto/GeneratedMessageLiteBuilderShrinker.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java index 16a8fadc5b..51de538e10 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java @@ -144,11 +144,13 @@ public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing ProgramMethod constructorMethod = superClass.lookupProgramMethod( references.generatedMessageLiteBuilderMethods.constructorMethod); - MethodAccessFlags constructorFlags = constructorMethod.getAccessFlags(); - if (!constructorFlags.isPublic()) { - constructorFlags.unsetPrivate(); - constructorFlags.unsetProtected(); - constructorFlags.setPublic(); + if (constructorMethod != null) { + MethodAccessFlags constructorFlags = constructorMethod.getAccessFlags(); + if (!constructorFlags.isPublic()) { + constructorFlags.unsetPrivate(); + constructorFlags.unsetProtected(); + constructorFlags.setPublic(); + } } superClass.accessFlags.demoteFromAbstract();