Merge commit 'e96740d1ac9f93e21fd1416fda48c9e69185a0e8' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 36adabe..ebebbf1 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -110,7 +110,7 @@
return;
}
if (command.isPrintVersion()) {
- Version.printToolVersion("D8");
+ System.out.println("D8 " + Version.getVersionString());
return;
}
InternalOptions options = command.getInternalOptions();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ab6796b..19a0118 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -656,8 +656,7 @@
// TODO(b/130721661): Enable this assert.
// assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
- assert appView.verticallyMergedClasses() == null
- || appView.verticallyMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
+ assert appView.allMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
processWhyAreYouKeepingAndCheckDiscarded(
appView.rootSet(),
@@ -886,7 +885,7 @@
return;
}
if (command.isPrintVersion()) {
- Version.printToolVersion("R8");
+ System.out.println("R8 " + Version.getVersionString());
return;
}
InternalOptions options = command.getInternalOptions();
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 9a9c4cb..918c373 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -16,21 +16,79 @@
private Version() {
}
- public static void printToolVersion(String toolName) {
- System.out.println(toolName + " " + Version.LABEL);
- System.out.println(VersionProperties.INSTANCE.getDescription());
+ /** Returns current R8 version (with additional info) as a string. */
+ public static String getVersionString() {
+ return LABEL + " (" + VersionProperties.INSTANCE.getDescription() + ")";
}
- /** Is this a development version of the D8/R8 library. */
- public static boolean isDev() {
+ /**
+ * Returns the major version number of the compiler.
+ *
+ * @return Major version or -1 for an unreleased build.
+ */
+ public static int getMajorVersion() {
+ if (LABEL.equals("master")) {
+ return -1;
+ }
+ int start = 0;
+ int end = LABEL.indexOf('.');
+ return Integer.parseInt(LABEL.substring(start, end));
+ }
+
+ /**
+ * Returns the minor version number of the compiler.
+ *
+ * @return Minor version or -1 for an unreleased build.
+ */
+ public static int getMinorVersion() {
+ if (LABEL.equals("master")) {
+ return -1;
+ }
+ int start = LABEL.indexOf('.') + 1;
+ int end = LABEL.indexOf('.', start);
+ return Integer.parseInt(LABEL.substring(start, end));
+ }
+
+ /**
+ * Returns the patch version number of the compiler.
+ *
+ * @return Patch version or -1 for an unreleased build.
+ */
+ public static int getPatchVersion() {
+ if (LABEL.equals("master")) {
+ return -1;
+ }
+ int skip = LABEL.indexOf('.') + 1;
+ int start = LABEL.indexOf('.', skip) + 1;
+ int end = LABEL.indexOf('.', start);
+ return Integer.parseInt(LABEL.substring(start, end));
+ }
+
+ /**
+ * Returns the pre-release version information of the compiler.
+ *
+ * @return Pre-release information if present, the empty string if absent, and null for an
+ * unreleased build.
+ */
+ public static String getPreReleaseString() {
+ if (LABEL.equals("master")) {
+ return null;
+ }
+ int start = LABEL.indexOf('-') + 1;
+ if (start > 0) {
+ return LABEL.substring(start);
+ }
+ return "";
+ }
+
+ /**
+ * Is this a development version of the D8/R8 library.
+ *
+ * @return True if the build is not a release or if it is a development release.
+ */
+ public static boolean isDevelopmentVersion() {
return LABEL.equals("master")
|| LABEL.endsWith("-dev")
|| VersionProperties.INSTANCE.isEngineering();
}
-
- /** Returns current R8 version (with additional info) as a string. */
- @SuppressWarnings("unused") // used by external tools to obtain R8 version
- public static String getVersionString() {
- return LABEL + " (" + VersionProperties.INSTANCE.getDescription() + ")";
- }
}
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 68e9e69..a52c2ea 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -326,7 +326,7 @@
return;
}
if (dexArgs.version) {
- Version.printToolVersion("CompatDx");
+ System.out.println("CompatDx " + Version.getVersionString());
return;
}
CompilationMode mode = CompilationMode.RELEASE;
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index 514f785..177f2ae 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -173,7 +173,7 @@
}
private static void printVersion() {
- Version.printToolVersion("CompatProguard");
+ System.out.println("CompatProguard " + Version.getVersionString());
}
private static void printHelp() {
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 6eb6a94..3b51579 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -535,9 +535,12 @@
public DexEncodedField resolveFieldOn(DexType type, DexField desc) {
assert checkIfObsolete();
DexClass holder = definitionFor(type);
- if (holder == null) {
- return null;
- }
+ return holder != null ? resolveFieldOn(holder, desc) : null;
+ }
+
+ public DexEncodedField resolveFieldOn(DexClass holder, DexField desc) {
+ assert checkIfObsolete();
+ assert holder != null;
// Step 1: Class declares the field.
DexEncodedField result = holder.lookupField(desc);
if (result != null) {
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 a2d6967..99a2a2b 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -5,6 +5,9 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
+import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
+import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
@@ -14,7 +17,6 @@
import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import com.google.common.base.Predicates;
@@ -52,6 +54,7 @@
private Predicate<DexType> classesEscapingIntoLibrary = Predicates.alwaysTrue();
private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
private Set<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.of();
+ private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
private VerticallyMergedClasses verticallyMergedClasses;
private AppView(
@@ -296,6 +299,28 @@
this.unneededVisibilityBridgeMethods = unneededVisibilityBridgeMethods;
}
+ public MergedClassesCollection allMergedClasses() {
+ MergedClassesCollection collection = new MergedClassesCollection();
+ if (horizontallyMergedLambdaClasses != null) {
+ collection.add(horizontallyMergedLambdaClasses);
+ }
+ if (verticallyMergedClasses != null) {
+ collection.add(verticallyMergedClasses);
+ }
+ return collection;
+ }
+
+ // Get the result of horizontal lambda class merging. Returns null if horizontal lambda class
+ // merging has not been run.
+ public HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses() {
+ return horizontallyMergedLambdaClasses;
+ }
+
+ public void setHorizontallyMergedLambdaClasses(
+ HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses) {
+ this.horizontallyMergedLambdaClasses = horizontallyMergedLambdaClasses;
+ }
+
// Get the result of vertical class merging. Returns null if vertical class merging has not been
// run.
public VerticallyMergedClasses verticallyMergedClasses() {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index eafa809..e2adfbb 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -107,6 +107,7 @@
ps.println("#");
ps.println("# Method: '" + methodName + "':");
writeAnnotations(method.annotations, ps);
+ ps.println("# " + method.accessFlags);
ps.println("#");
ps.println();
Code code = method.getCode();
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 47930c2..5a7e785 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1092,15 +1092,6 @@
return m1.method.slowCompareTo(m2.method);
}
- public static class ClassInlinerEligibility {
-
- public final boolean returnsReceiver;
-
- public ClassInlinerEligibility(boolean returnsReceiver) {
- this.returnsReceiver = returnsReceiver;
- }
- }
-
public MethodOptimizationInfo getOptimizationInfo() {
checkIfObsolete();
return optimizationInfo;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 523fd85..488b6c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -34,7 +34,8 @@
private String toStringCache = null;
DexType(DexString descriptor) {
- assert !descriptor.toString().contains(".");
+ assert !descriptor.toString().contains(".")
+ : "Malformed descriptor: " + descriptor.toString();
this.descriptor = descriptor;
}
@@ -180,12 +181,7 @@
}
public boolean isPrimitiveType() {
- return isPrimitiveType((char) descriptor.content[0]);
- }
-
- private boolean isPrimitiveType(char c) {
- return c == 'Z' || c == 'B' || c == 'S' || c == 'C' || c == 'I' || c == 'F' || c == 'J'
- || c == 'D';
+ return DescriptorUtils.isPrimitiveType((char) descriptor.content[0]);
}
public boolean isVoidType() {
@@ -244,7 +240,7 @@
if (!isArrayType()) {
return false;
}
- return isPrimitiveType((char) descriptor.content[1]);
+ return DescriptorUtils.isPrimitiveType((char) descriptor.content[1]);
}
public boolean isWideType() {
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
new file mode 100644
index 0000000..1facc45
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
@@ -0,0 +1,30 @@
+// 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.graph.classmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Set;
+
+public class HorizontallyMergedLambdaClasses implements MergedClasses {
+
+ private final Set<DexType> sources;
+
+ public HorizontallyMergedLambdaClasses(Set<DexType> sources) {
+ this.sources = sources;
+ }
+
+ @Override
+ public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
+ for (DexType source : sources) {
+ assert appView.appInfo().wasPruned(source)
+ : "Expected horizontally merged lambda class `"
+ + source.toSourceString()
+ + "` to be absent";
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
new file mode 100644
index 0000000..7b9bba3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
@@ -0,0 +1,13 @@
+// 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.graph.classmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public interface MergedClasses {
+
+ boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
new file mode 100644
index 0000000..ad7c71d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
@@ -0,0 +1,27 @@
+// 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.graph.classmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MergedClassesCollection implements MergedClasses {
+
+ private List<MergedClasses> collection = new ArrayList<>();
+
+ public void add(MergedClasses mergedClasses) {
+ collection.add(mergedClasses);
+ }
+
+ @Override
+ public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
+ for (MergedClasses mergedClasses : collection) {
+ assert mergedClasses.verifyAllSourcesPruned(appView);
+ }
+ 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
new file mode 100644
index 0000000..237f9ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
@@ -0,0 +1,52 @@
+// 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.graph.classmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class VerticallyMergedClasses implements MergedClasses {
+
+ private final Map<DexType, DexType> mergedClasses;
+ private final Map<DexType, List<DexType>> sources;
+
+ public VerticallyMergedClasses(Map<DexType, DexType> mergedClasses) {
+ Map<DexType, List<DexType>> sources = Maps.newIdentityHashMap();
+ mergedClasses.forEach(
+ (source, target) -> sources.computeIfAbsent(target, key -> new ArrayList<>()).add(source));
+ this.mergedClasses = mergedClasses;
+ this.sources = sources;
+ }
+
+ public List<DexType> getSourcesFor(DexType type) {
+ return sources.getOrDefault(type, ImmutableList.of());
+ }
+
+ public DexType getTargetFor(DexType type) {
+ assert mergedClasses.containsKey(type);
+ return mergedClasses.get(type);
+ }
+
+ public boolean hasBeenMergedIntoSubtype(DexType type) {
+ return mergedClasses.containsKey(type);
+ }
+
+ @Override
+ public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
+ for (List<DexType> sourcesForTarget : sources.values()) {
+ for (DexType source : sourcesForTarget) {
+ assert appView.appInfo().wasPruned(source)
+ : "Expected vertically merged class `" + source.toSourceString() + "` to be absent";
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index d9f4e2d..0605623 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -109,7 +109,7 @@
if (isConstantArrayThroughoutMethod(root, assumedNotToDependOnEnvironment)) {
return false;
}
- if (root.getAbstractValue(appView).isSingleEnumValue()) {
+ if (root.getAbstractValue(appView, context).isSingleEnumValue()) {
return false;
}
if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 8e327aa..4adc57e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -83,6 +83,11 @@
return new ClassTypeLatticeElement(type, nullability, lazyInterfaces, variants, appView);
}
+ public boolean isRelatedTo(ClassTypeLatticeElement other, AppView<?> appView) {
+ return lessThanOrEqualUpToNullability(other, appView)
+ || other.lessThanOrEqualUpToNullability(this, appView);
+ }
+
@Override
public ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability) {
ClassTypeLatticeElement variant = variants.get(nullability);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index a4c8d4b..b2273e9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -559,13 +559,30 @@
}
public int numberInstructions(int nextInstructionNumber) {
+ return numberInstructions(nextInstructionNumber, INSTRUCTION_NUMBER_DELTA);
+ }
+
+ public int numberInstructions(int nextInstructionNumber, int increment) {
for (Instruction instruction : instructions) {
instruction.setNumber(nextInstructionNumber);
- nextInstructionNumber += INSTRUCTION_NUMBER_DELTA;
+ nextInstructionNumber += increment;
}
return nextInstructionNumber;
}
+ public void clearInstructionNumbers() {
+ for (Instruction instruction : instructions) {
+ instruction.clearNumber();
+ }
+ }
+
+ public boolean hasNoInstructionNumbers() {
+ for (Instruction instruction : instructions) {
+ assert instruction.getNumber() == -1;
+ }
+ return true;
+ }
+
public LinkedList<Instruction> getInstructions() {
return instructions;
}
@@ -1881,7 +1898,7 @@
* {@code target} is the same block than the current {@link BasicBlock}.
*/
public boolean hasPathTo(BasicBlock target) {
- List<BasicBlock> visitedBlocks = new ArrayList<>();
+ Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
ArrayDeque<BasicBlock> blocks = new ArrayDeque<>();
blocks.push(this);
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 8740a9e..805aad9 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
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.TypeChecker;
import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
@@ -17,7 +18,6 @@
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -958,6 +958,25 @@
return blocks;
}
+ public void numberInstructionsPerBlock() {
+ for (BasicBlock block : blocks) {
+ block.numberInstructions(0, 1);
+ }
+ }
+
+ public void clearInstructionNumbers() {
+ for (BasicBlock block : blocks) {
+ block.clearInstructionNumbers();
+ }
+ }
+
+ public boolean hasNoInstructionNumbers() {
+ for (BasicBlock block : blocks) {
+ assert block.hasNoInstructionNumbers();
+ }
+ return true;
+ }
+
public int numberRemainingInstructions() {
for (Instruction instruction : instructions()) {
if (instruction.getNumber() == -1) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index 3fc4de0..f6054e1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -201,4 +201,9 @@
public boolean mayHaveStringSwitch() {
return get(Opcodes.STRING_SWITCH);
}
+
+ public boolean mayHaveArithmeticOrLogicalBinop() {
+ // TODO(b/7145202413): Implement this.
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 228dc2b..6893464 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -362,6 +362,10 @@
this.number = number;
}
+ public void clearNumber() {
+ this.number = -1;
+ }
+
/**
* Compare equality of two class-equivalent instructions modulo their values and positions.
*/
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index c7aeff1..f9ca31d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -9,7 +9,6 @@
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.DexEncodedField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
@@ -847,7 +846,7 @@
return definition.isOutConstant() && !hasLocalInfo();
}
- public AbstractValue getAbstractValue(AppView<?> appView) {
+ public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
if (!appView.enableWholeProgramOptimizations()) {
return UnknownValue.getInstance();
}
@@ -857,15 +856,7 @@
return UnknownValue.getInstance();
}
- if (root.definition.isFieldGet()) {
- FieldInstruction fieldGet = root.definition.asFieldInstruction();
- DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField());
- if (field != null) {
- return field.getOptimizationInfo().getAbstractValue();
- }
- }
-
- return UnknownValue.getInstance();
+ return root.definition.getAbstractValue(appView, context);
}
public boolean isPhi() {
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 7a64973..fd1b111 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
@@ -1106,7 +1106,7 @@
}
if (lambdaMerger != null) {
- lambdaMerger.rewriteCode(method, code);
+ lambdaMerger.rewriteCode(method, code, inliner);
assert code.isConsistentSSA();
}
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 3092293..21f800c 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
@@ -30,6 +30,7 @@
import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -61,7 +62,6 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashSet;
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 6e96719..0f6bd4f 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
@@ -6,12 +6,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -57,7 +57,7 @@
void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
- void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
+ void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility);
void setInitializerInfo(DexEncodedMethod method, InitializerInfo info);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 4da2dcd..49b11a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -91,12 +91,18 @@
if (!resolutionResult.isValidVirtualTarget(appView.options())) {
continue;
}
+ // If the resolution ended up with a single target, check if it is a library override.
+ // And if so, bail out early (to avoid expensive target lookup).
+ if (resolutionResult.hasSingleTarget()
+ && isLibraryMethodOrLibraryMethodOverride(resolutionResult.getSingleTarget())) {
+ continue;
+ }
}
Collection<DexEncodedMethod> targets = invoke.lookupTargets(appView, context.method.holder);
assert invoke.isInvokeMethodWithDynamicDispatch()
// For other invocation types, the size of targets should be at most one.
|| targets == null || targets.size() <= 1;
- if (targets == null || targets.isEmpty()) {
+ if (targets == null || targets.isEmpty() || hasLibraryOverrides(targets)) {
continue;
}
for (DexEncodedMethod target : targets) {
@@ -110,7 +116,7 @@
Collection<DexEncodedMethod> targets =
appView.appInfo().lookupLambdaImplementedMethods(
instruction.asInvokeCustom().getCallSite());
- if (targets == null) {
+ if (targets == null || targets.isEmpty() || hasLibraryOverrides(targets)) {
continue;
}
for (DexEncodedMethod target : targets) {
@@ -120,6 +126,30 @@
}
}
+ // TODO(b/140204899): Instead of reprocessing here, pass stopping criteria to lookup?
+ // If any of target method is a library method override, bail out entirely/early.
+ private boolean hasLibraryOverrides(Collection<DexEncodedMethod> targets) {
+ for (DexEncodedMethod target : targets) {
+ if (isLibraryMethodOrLibraryMethodOverride(target)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isLibraryMethodOrLibraryMethodOverride(DexEncodedMethod target) {
+ // Not a program method.
+ if (!target.isProgramMethod(appView)) {
+ return true;
+ }
+ // If the method overrides a library method, it is unsure how the method would be invoked by
+ // that library.
+ if (target.isLibraryMethodOverride().isTrue()) {
+ return true;
+ }
+ return false;
+ }
+
// Record arguments for the given method if necessary.
// At the same time, if it decides to bail out, make the corresponding info immutable so that we
// can avoid recording arguments for the same method accidentally.
@@ -142,9 +172,19 @@
if (appView.appInfo().isPinned(target.method)) {
return CallSiteOptimizationInfo.TOP;
}
+ // Not a program method.
+ if (!target.isProgramMethod(appView)) {
+ // But, should not be reachable, since we already bail out.
+ assert false
+ : "Trying to compute call site optimization info for " + target.toSourceString();
+ return CallSiteOptimizationInfo.TOP;
+ }
// If the method overrides a library method, it is unsure how the method would be invoked by
// that library.
if (target.isLibraryMethodOverride().isTrue()) {
+ // But, should not be reachable, since we already bail out.
+ assert false
+ : "Trying to compute call site optimization info for " + target.toSourceString();
return CallSiteOptimizationInfo.TOP;
}
// If the program already has illegal accesses, method resolution results will reflect that too.
@@ -165,11 +205,9 @@
return;
}
// TODO(b/139246447): Assert no BOTTOM left.
- if (!callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()) {
+ if (!callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, code.method)) {
return;
}
- assert callSiteOptimizationInfo.asConcreteCallSiteOptimizationInfo()
- .hasUsefulOptimizationInfo(appView, code.method);
Set<Value> affectedValues = Sets.newIdentityHashSet();
List<Assume<?>> assumeInstructions = new LinkedList<>();
List<Instruction> constants = new LinkedList<>();
@@ -264,17 +302,16 @@
for (DexProgramClass clazz : appView.appInfo().classes()) {
for (DexEncodedMethod method : clazz.methods()) {
assert !method.isObsolete();
- if (method.shouldNotHaveCode()) {
- assert !method.hasCode();
+ if (method.shouldNotHaveCode()
+ || !method.hasCode()
+ || method.getCode().isEmptyVoidMethod()) {
continue;
}
// TODO(b/139246447): Assert no BOTTOM left.
CallSiteOptimizationInfo callSiteOptimizationInfo = method.getCallSiteOptimizationInfo();
- if (!callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()) {
+ if (!callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, method)) {
continue;
}
- assert callSiteOptimizationInfo.asConcreteCallSiteOptimizationInfo()
- .hasUsefulOptimizationInfo(appView, method);
targetsToRevisit.add(method);
if (appView.options().testing.callSiteOptimizationInfoInspector != null) {
appView.options().testing.callSiteOptimizationInfoInspector.accept(method);
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 e1c583d..d48d1f2 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
@@ -1635,28 +1635,61 @@
}
/**
- * If an instruction is known to be a /lit8 or /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.
+ * 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;
+ }
+
+ code.numberInstructionsPerBlock();
+
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<Value> binopsWithLit8OrLit16NonConstantValues = Sets.newIdentityHashSet();
while (instructionIterator.hasNext()) {
Instruction currentInstruction = instructionIterator.next();
- if (shouldBeLitInstruction(currentInstruction)) {
- assert currentInstruction.isBinop();
- Binop binop = currentInstruction.asBinop();
- Value constValue;
- if (binop.leftValue().isConstNumber()) {
- constValue = binop.leftValue();
- } else if (binop.rightValue().isConstNumber()) {
- constValue = binop.rightValue();
- } else {
- throw new Unreachable();
+ 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<Value> lastUseOfBinopsWithLit8OrLit16NonConstantValues =
+ new Reference2IntOpenHashMap<>();
+ lastUseOfBinopsWithLit8OrLit16NonConstantValues.defaultReturnValue(-1);
+ while (instructionIterator.hasPrevious()) {
+ Instruction currentInstruction = instructionIterator.previous();
+ for (Value value : Iterables.concat(currentInstruction.inValues(), currentInstruction.getDebugValues())) {
+ if (!binopsWithLit8OrLit16NonConstantValues.contains(value)) {
+ continue;
}
+ if (!lastUseOfBinopsWithLit8OrLit16NonConstantValues.containsKey(value)) {
+ lastUseOfBinopsWithLit8OrLit16NonConstantValues.put(
+ value, currentInstruction.getNumber());
+ }
+ }
+ }
+ // Do the transformation except if the binop can use the binop/2addr format.
+ while (instructionIterator.hasNext()) {
+ Instruction currentInstruction = instructionIterator.next();
+ if (!isBinopWithLit8OrLit16(currentInstruction)) {
+ continue;
+ }
+ Binop binop = currentInstruction.asBinop();
+ if (!canBe2AddrInstruction(binop, 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
@@ -1673,24 +1706,48 @@
}
}
+ code.clearInstructionNumbers();
+ assert code.hasNoInstructionNumbers();
+
assert code.isConsistentSSA();
}
- /**
- * A /lit8 or /lit16 instruction only concerns arithmetic or logical instruction. /lit8 or /lit16
- * instructions generate bigger code than 2addr instructions, thus we favor 2addr instructions
- * rather than /lit8 or /lit16 instructions.
- */
- private static boolean shouldBeLitInstruction(Instruction instruction) {
- if (instruction.isArithmeticBinop() || instruction.isLogicalBinop()) {
- Binop binop = instruction.asBinop();
- if (!binop.needsValueInRegister(binop.leftValue()) ||
- !binop.needsValueInRegister(binop.rightValue())) {
- return !canBe2AddrInstruction(binop);
- }
+ // 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 false;
+ // 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();
+ }
}
/**
@@ -1698,28 +1755,28 @@
* 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) {
- Value value = null;
- if (binop.needsValueInRegister(binop.leftValue())) {
- value = binop.leftValue();
- } else if (binop.isCommutative() && binop.needsValueInRegister(binop.rightValue())) {
- value = binop.rightValue();
+ private static boolean canBe2AddrInstruction(
+ Binop binop, Reference2IntMap<Value> lastUseOfRelevantValue) {
+ Value value = binopWithLit8OrLit16NonConstant(binop);
+ assert value != null;
+ int number = lastUseOfRelevantValue.getInt(value);
+ if (number > 0 && number > binop.getNumber()) {
+ return false;
+ }
+ Iterable<Instruction> users =
+ value.debugUsers() != null
+ ? Iterables.concat(value.uniqueUsers(), value.debugUsers())
+ : value.uniqueUsers();
+
+ for (Instruction user : users) {
+ if (hasPath(binop, user)) {
+ return false;
+ }
}
- if (value != null) {
- Iterable<Instruction> users = value.debugUsers() != null ?
- Iterables.concat(value.uniqueUsers(), value.debugUsers()) : value.uniqueUsers();
-
- for (Instruction user : users) {
- if (hasPath(binop, user)) {
- return false;
- }
- }
-
- for (Phi user : value.uniquePhiUsers()) {
- if (binop.getBlock().hasPathTo(user.getBlock())) {
- return false;
- }
+ for (Phi user : value.uniquePhiUsers()) {
+ if (binop.getBlock().hasPathTo(user.getBlock())) {
+ return false;
}
}
@@ -1734,8 +1791,10 @@
BasicBlock sourceBlock = source.getBlock();
BasicBlock targetBlock = target.getBlock();
if (sourceBlock == targetBlock) {
- return sourceBlock.getInstructions().indexOf(source) <
- targetBlock.getInstructions().indexOf(target);
+ // Instructions must be numbered when getting here.
+ assert source.getNumber() != -1;
+ assert target.getNumber() != -1;
+ return source.getNumber() < target.getNumber();
}
return source.getBlock().hasPathTo(targetBlock);
@@ -2542,9 +2601,10 @@
}
}
} else {
- AbstractValue abstractValue = lhs.getAbstractValue(appView);
+ DexType context = code.method.method.holder;
+ AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
if (abstractValue.isSingleEnumValue()) {
- AbstractValue otherAbstractValue = rhs.getAbstractValue(appView);
+ AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
if (abstractValue == otherAbstractValue) {
simplifyIfWithKnownCondition(code, block, theIf, 0);
} else if (otherAbstractValue.isSingleEnumValue()) {
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 f53d6d8..1382317 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
@@ -131,7 +131,7 @@
}
// Only canonicalize enum values. This is only OK if the instruction cannot have side effects.
if (current.isStaticGet()) {
- if (!current.outValue().getAbstractValue(appView).isSingleEnumValue()) {
+ if (!current.outValue().getAbstractValue(appView, context).isSingleEnumValue()) {
continue;
}
if (current.instructionMayHaveSideEffects(appView, context)) {
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 c3fe726..6a9c3af 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
@@ -812,6 +812,14 @@
public void performForcedInlining(
DexEncodedMethod method,
IRCode code,
+ Map<? extends InvokeMethod, InliningInfo> invokesToInline) {
+ performForcedInlining(
+ method, code, invokesToInline, new InliningIRProvider(appView, method, code));
+ }
+
+ public void performForcedInlining(
+ DexEncodedMethod method,
+ IRCode code,
Map<? extends InvokeMethod, InliningInfo> invokesToInline,
InliningIRProvider inliningIRProvider) {
ForcedInliningOracle oracle = new ForcedInliningOracle(appView, method, invokesToInline);
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 2945c6e..ddb7e99 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
@@ -220,16 +220,25 @@
continue;
}
+ assert processor.getReceivers().verifyReceiverSetsAreDisjoint();
+
// Is inlining allowed.
- if (processor.getEstimatedCombinedSizeForInlining()
- >= appView.options().classInliningInstructionAllowance) {
+ InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+ ClassInlinerCostAnalysis costAnalysis =
+ new ClassInlinerCostAnalysis(
+ appView, inliningIRProvider, processor.getReceivers().getDefiniteReceiverAliases());
+ if (costAnalysis.willExceedInstructionBudget(
+ code, processor.getDirectInlinees(), processor.getIndirectInlinees())) {
+ // This root is unlikely to be inlined in the future.
+ rootsIterator.remove();
continue;
}
// Inline the class instance.
- InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
+ assert inliningIRProvider.verifyIRCacheIsEmpty();
+
// Restore normality.
Set<Value> affectedValues = Sets.newIdentityHashSet();
code.removeAllTrivialPhis(affectedValues);
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
new file mode 100644
index 0000000..9b9670e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -0,0 +1,149 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Analysis that estimates the cost of class inlining an object allocation. */
+class ClassInlinerCostAnalysis {
+
+ private final AppView<?> appView;
+ private final InliningIRProvider inliningIRProvider;
+ private final Set<Value> definiteReceiverAliases;
+
+ private int estimatedCost = 0;
+
+ ClassInlinerCostAnalysis(
+ AppView<?> appView,
+ InliningIRProvider inliningIRProvider,
+ Set<Value> definiteReceiverAliases) {
+ this.appView = appView;
+ this.inliningIRProvider = inliningIRProvider;
+ this.definiteReceiverAliases = definiteReceiverAliases;
+ }
+
+ boolean willExceedInstructionBudget(
+ IRCode code,
+ Map<InvokeMethod, DexEncodedMethod> directInlinees,
+ List<DexEncodedMethod> indirectInlinees) {
+ for (DexEncodedMethod inlinee : indirectInlinees) {
+ // We do not have the corresponding invoke instruction for the inlinees that are not called
+ // directly from `code` (these are called indirectly from one of the methods in
+ // `directInlinees`). Therefore, we currently choose not to build IR for estimating the number
+ // of non-materializing instructions, since we cannot cache the IR (it would have the wrong
+ // position).
+ int increment = inlinee.getCode().estimatedSizeForInlining();
+ if (exceedsInstructionBudgetAfterIncrement(increment)) {
+ return true;
+ }
+ }
+
+ // Visit the direct inlinees in a deterministic order to ensure that the state of the value
+ // number generated is deterministic for each inlinee.
+ int numberOfSeenDirectInlinees = 0;
+ int numberOfDirectInlinees = directInlinees.size();
+ for (InvokeMethod invoke : code.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
+ DexEncodedMethod inlinee = directInlinees.get(invoke);
+ if (inlinee == null) {
+ // Not a direct inlinee.
+ continue;
+ }
+ IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
+ int increment =
+ inlinee.getCode().estimatedSizeForInlining()
+ - estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
+ assert increment >= 0;
+ if (exceedsInstructionBudgetAfterIncrement(increment)) {
+ return true;
+ }
+ if (++numberOfSeenDirectInlinees == numberOfDirectInlinees) {
+ break;
+ }
+ }
+
+ // Verify that all direct inlinees have been visited.
+ assert numberOfSeenDirectInlinees == numberOfDirectInlinees;
+
+ return false;
+ }
+
+ private boolean exceedsInstructionBudgetAfterIncrement(int increment) {
+ estimatedCost += increment;
+ return estimatedCost > appView.options().classInliningInstructionAllowance;
+ }
+
+ // TODO(b/143176500): Do not include instructions that will be canonicalized after inlining.
+ // Take care of that fact that after inlining the first method, this could introduce a constant
+ // in the caller, which could then lead to a constant in the second inlinee being canonicalized.
+ // TODO(b/143176500): Do not include instructions that will be dead code eliminated as a result of
+ // constant arguments.
+ private int estimateNumberOfNonMaterializingInstructions(InvokeMethod invoke, IRCode inlinee) {
+ int result = 0;
+ Set<Value> receiverAliasesInInlinee = null;
+ for (Instruction instruction : inlinee.instructions()) {
+ switch (instruction.opcode()) {
+ case ARGUMENT:
+ // Intentionally not counted as a non-materializing instruction, since there are no
+ // argument instructions in the CF/DEX code.
+ break;
+
+ case INSTANCE_GET:
+ case INSTANCE_PUT:
+ // Will not materialize after class inlining if the object is the "root" instance.
+ Value object =
+ instruction.isInstanceGet()
+ ? instruction.asInstanceGet().object()
+ : instruction.asInstancePut().object();
+ Value root = object.getAliasedValue();
+ if (receiverAliasesInInlinee == null) {
+ receiverAliasesInInlinee = getReceiverAliasesInInlinee(invoke, inlinee);
+ }
+ if (receiverAliasesInInlinee.contains(root)) {
+ result++;
+ }
+ break;
+
+ case RETURN:
+ // Wil not materialize after class inlining.
+ result++;
+ break;
+
+ default:
+ // Will materialize.
+ break;
+ }
+ }
+ return result;
+ }
+
+ private Set<Value> getReceiverAliasesInInlinee(InvokeMethod invoke, IRCode inlinee) {
+ List<Value> arguments = inlinee.collectArguments();
+ Set<Value> receiverAliasesInInlinee = Sets.newIdentityHashSet();
+ for (int i = 0; i < invoke.inValues().size(); i++) {
+ Value inValue = invoke.inValues().get(i);
+ if (definiteReceiverAliases.contains(inValue)) {
+ receiverAliasesInInlinee.add(arguments.get(i));
+ } else {
+ assert !definiteReceiverAliases.contains(inValue.getAliasedValue());
+ }
+ }
+ return receiverAliasesInInlinee;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
new file mode 100644
index 0000000..a80a9ec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
@@ -0,0 +1,21 @@
+// 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.classinliner;
+
+import com.android.tools.r8.utils.OptionalBool;
+
+public class ClassInlinerEligibilityInfo {
+
+ /**
+ * Set to {@link OptionalBool#TRUE} if the method is guaranteed to return the receiver, {@link
+ * OptionalBool#FALSE} if the method is guaranteed not to return the receiver, and {@link
+ * OptionalBool#UNKNOWN} if the method may return the receiver.
+ */
+ final OptionalBool returnsReceiver;
+
+ public ClassInlinerEligibilityInfo(OptionalBool returnsReceiver) {
+ this.returnsReceiver = returnsReceiver;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
new file mode 100644
index 0000000..f6e6e70
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
@@ -0,0 +1,135 @@
+// 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.classinliner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+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.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BooleanLatticeElement;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This analysis determines whether a method returns the receiver. The analysis is specific to the
+ * class inliner, and the result is therefore not sound in general.
+ *
+ * <p>The analysis makes the following assumptions.
+ *
+ * <ul>
+ * <li>None of the given method's arguments is an alias of the receiver (except for the receiver
+ * itself).
+ * <li>The receiver is not stored in the heap. Thus, it is guaranteed that (i) all field-get
+ * instructions do not return an alias of the receiver, and (ii) invoke instructions can only
+ * return an alias of the receiver if the receiver is given as an argument.
+ * </ul>
+ */
+public class ClassInlinerReceiverAnalysis {
+
+ private final AppView<?> appView;
+ private final DexEncodedMethod method;
+ private final IRCode code;
+ private final Value receiver;
+
+ private final Map<Value, OptionalBool> isReceiverAliasCache = new IdentityHashMap<>();
+
+ public ClassInlinerReceiverAnalysis(AppView<?> appView, DexEncodedMethod method, IRCode code) {
+ this.appView = appView;
+ this.method = method;
+ this.code = code;
+ this.receiver = code.getThis();
+ assert !receiver.hasAliasedValue();
+ assert receiver.getTypeLattice().isClassType();
+ }
+
+ public OptionalBool computeReturnsReceiver() {
+ if (method.method.proto.returnType.isVoidType()) {
+ return OptionalBool.FALSE;
+ }
+
+ List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
+ if (normalExitBlocks.isEmpty()) {
+ return OptionalBool.FALSE;
+ }
+
+ BooleanLatticeElement result = OptionalBool.BOTTOM;
+ for (BasicBlock block : normalExitBlocks) {
+ Value returnValue = block.exit().asReturn().returnValue();
+ result = result.join(getOrComputeIsReceiverAlias(returnValue));
+
+ // Stop as soon as we reach unknown.
+ if (result.isUnknown()) {
+ return OptionalBool.UNKNOWN;
+ }
+ }
+ assert !result.isBottom();
+ return result.asOptionalBool();
+ }
+
+ private OptionalBool getOrComputeIsReceiverAlias(Value value) {
+ Value root = value.getAliasedValue();
+ return isReceiverAliasCache.computeIfAbsent(root, this::computeIsReceiverAlias);
+ }
+
+ private OptionalBool computeIsReceiverAlias(Value value) {
+ assert !value.hasAliasedValue();
+
+ if (value == receiver) {
+ // Guaranteed to return the receiver.
+ return OptionalBool.TRUE;
+ }
+
+ ClassTypeLatticeElement valueType = value.getTypeLattice().asClassTypeLatticeElement();
+ if (valueType == null) {
+ return OptionalBool.FALSE;
+ }
+
+ ClassTypeLatticeElement receiverType = receiver.getTypeLattice().asClassTypeLatticeElement();
+ if (!valueType.isRelatedTo(receiverType, appView)) {
+ // Guaranteed not to return the receiver.
+ return OptionalBool.FALSE;
+ }
+
+ if (value.isPhi()) {
+ // Not sure what is returned.
+ return OptionalBool.UNKNOWN;
+ }
+
+ Instruction definition = value.definition;
+ if (definition.isArrayGet() || definition.isFieldGet()) {
+ // Guaranteed not to return the receiver, since the class inliner does not allow the
+ // receiver to flow into any arrays or fields.
+ return OptionalBool.FALSE;
+ }
+
+ if (definition.isConstInstruction() || definition.isCreatingInstanceOrArray()) {
+ // Guaranteed not to return the receiver.
+ return OptionalBool.FALSE;
+ }
+
+ if (definition.isInvokeMethod()) {
+ // Since the class inliner does not allow the receiver to flow into the heap, the only way for
+ // the invoked method to return the receiver is if one of the given arguments is an alias of
+ // the receiver.
+ InvokeMethod invoke = definition.asInvokeMethod();
+ for (Value argument : invoke.arguments()) {
+ if (getOrComputeIsReceiverAlias(argument).isPossiblyTrue()) {
+ return OptionalBool.UNKNOWN;
+ }
+ }
+
+ return OptionalBool.FALSE;
+ }
+
+ // Not sure what is returned.
+ return OptionalBool.UNKNOWN;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
new file mode 100644
index 0000000..5a8c59d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
@@ -0,0 +1,127 @@
+// 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.classinliner;
+
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.AliasKind;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+
+class ClassInlinerReceiverSet {
+
+ private final Value root;
+
+ private final Set<Value> definiteReceiverAliases;
+ private final Set<Value> maybeReceiverAliases = Sets.newIdentityHashSet();
+
+ // Set of values that are not allowed to become an alias of the receiver.
+ private final Set<Value> illegalReceiverAliases = Sets.newIdentityHashSet();
+
+ // Set of values that are allowed to become an alias of the receiver under certain circumstances.
+ private final Map<Value, List<BooleanSupplier>> deferredAliasValidityChecks =
+ new IdentityHashMap<>();
+
+ ClassInlinerReceiverSet(Value root) {
+ this.definiteReceiverAliases = SetUtils.newIdentityHashSet(root);
+ this.root = root;
+ }
+
+ Set<Value> getDefiniteReceiverAliases() {
+ return definiteReceiverAliases;
+ }
+
+ Set<Value> getMaybeReceiverAliases() {
+ return maybeReceiverAliases;
+ }
+
+ boolean addReceiverAlias(Value alias, AliasKind kind) {
+ if (isIllegalReceiverAlias(alias)) {
+ return false; // Not allowed.
+ }
+ // All checks passed.
+ deferredAliasValidityChecks.remove(alias);
+ boolean changed;
+ if (kind == AliasKind.DEFINITE) {
+ assert !maybeReceiverAliases.contains(alias);
+ changed = definiteReceiverAliases.add(alias);
+ } else {
+ assert !definiteReceiverAliases.contains(alias);
+ changed = maybeReceiverAliases.add(alias);
+ }
+ // Verify that the state changed. Otherwise, we are analyzing the same instruction more than
+ // once.
+ assert changed : alias.toString() + " already added as an alias";
+ return true;
+ }
+
+ boolean addIllegalReceiverAlias(Value value) {
+ if (isReceiverAlias(value)) {
+ return false;
+ }
+ illegalReceiverAliases.add(value);
+ // Since `value` is never allowed as a receiver, there is no need to keep the validity checks
+ // around.
+ deferredAliasValidityChecks.remove(value);
+ return true;
+ }
+
+ void addDeferredAliasValidityCheck(Value value, BooleanSupplier deferredValidityCheck) {
+ assert !isReceiverAlias(value);
+ // Only add the deferred validity check if `value` may be allowed as a receiver (i.e., it is not
+ // already illegal).
+ if (illegalReceiverAliases.contains(value)) {
+ assert !deferredAliasValidityChecks.containsKey(value);
+ } else {
+ deferredAliasValidityChecks
+ .computeIfAbsent(value, ignore -> new ArrayList<>())
+ .add(deferredValidityCheck);
+ }
+ }
+
+ boolean isReceiverAlias(Value value) {
+ return isDefiniteReceiverAlias(value) || isMaybeReceiverAlias(value);
+ }
+
+ boolean isDefiniteReceiverAlias(Value value) {
+ return definiteReceiverAliases.contains(value);
+ }
+
+ private boolean isMaybeReceiverAlias(Value value) {
+ return maybeReceiverAliases.contains(value);
+ }
+
+ private boolean isIllegalReceiverAlias(Value value) {
+ if (illegalReceiverAliases.contains(value)) {
+ return true;
+ }
+ List<BooleanSupplier> deferredValidityChecks = deferredAliasValidityChecks.get(value);
+ if (deferredValidityChecks != null) {
+ for (BooleanSupplier deferredValidityCheck : deferredValidityChecks) {
+ if (!deferredValidityCheck.getAsBoolean()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void reset() {
+ deferredAliasValidityChecks.clear();
+ definiteReceiverAliases.clear();
+ definiteReceiverAliases.add(root);
+ maybeReceiverAliases.clear();
+ }
+
+ boolean verifyReceiverSetsAreDisjoint() {
+ assert Sets.intersection(getMaybeReceiverAliases(), getDefiniteReceiverAliases()).isEmpty();
+ return true;
+ }
+}
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 096699b..36112c9 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
@@ -4,11 +4,12 @@
package com.android.tools.r8.ir.optimize.classinliner;
+
import com.android.tools.r8.errors.Unreachable;
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.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -21,6 +22,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionOrPhi;
import com.android.tools.r8.ir.code.Invoke.Type;
@@ -43,9 +45,7 @@
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -63,6 +63,12 @@
import java.util.function.Supplier;
final class InlineCandidateProcessor {
+
+ enum AliasKind {
+ DEFINITE,
+ MAYBE
+ }
+
private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES =
ImmutableSet.of(If.Type.EQ, If.Type.NE);
@@ -86,12 +92,12 @@
private final List<Pair<InvokeMethod, Integer>> unusedArguments
= new ArrayList<>();
- private int estimatedCombinedSizeForInlining = 0;
+ private final Map<InvokeMethod, DexEncodedMethod> directInlinees = new IdentityHashMap<>();
+ private final List<DexEncodedMethod> indirectInlinees = new ArrayList<>();
- // Set of values that may be an alias of the "root" instance (including the root instance itself).
- // TODO(b/144825216): Distinguish the "may-aliases" from the "must-aliases" such that the cost
- // analysis is not optimistic.
- private final Set<Value> receivers;
+ // Sets of values that must/may be an alias of the "root" instance (including the root instance
+ // itself).
+ private final ClassInlinerReceiverSet receivers;
InlineCandidateProcessor(
AppView<AppInfoWithLiveness> appView,
@@ -108,14 +114,18 @@
this.method = method;
this.root = root;
this.isProcessedConcurrently = isProcessedConcurrently;
- this.receivers = SetUtils.newIdentityHashSet(root.outValue());
+ this.receivers = new ClassInlinerReceiverSet(root.outValue());
}
- int getEstimatedCombinedSizeForInlining() {
- return estimatedCombinedSizeForInlining;
+ Map<InvokeMethod, DexEncodedMethod> getDirectInlinees() {
+ return directInlinees;
}
- Set<Value> getReceivers() {
+ List<DexEncodedMethod> getIndirectInlinees() {
+ return indirectInlinees;
+ }
+
+ ClassInlinerReceiverSet getReceivers() {
return receivers;
}
@@ -282,24 +292,28 @@
for (Instruction user : currentUsers) {
if (user.isAssume()) {
Value alias = user.outValue();
+ if (receivers.isReceiverAlias(alias)) {
+ continue; // Already processed.
+ }
if (alias.hasPhiUsers()) {
return alias.firstPhiUser(); // Not eligible.
}
- receivers.add(alias);
+ if (!receivers.addReceiverAlias(alias, AliasKind.DEFINITE)) {
+ return user; // Not eligible.
+ }
indirectUsers.addAll(alias.uniqueUsers());
continue;
}
// Field read/write.
if (user.isInstanceGet()
- || (user.isInstancePut() && user.asInstancePut().value() != eligibleInstance)) {
- DexField field = user.asFieldInstruction().getField();
- if (field.holder == eligibleClass
- && eligibleClassDefinition.lookupInstanceField(field) != null) {
- // Since class inliner currently only supports classes directly extending
- // java.lang.Object, we don't need to worry about fields defined in superclasses.
- continue;
+ || (user.isInstancePut()
+ && receivers.addIllegalReceiverAlias(user.asInstancePut().value()))) {
+ DexEncodedField field =
+ appView.appInfo().resolveField(user.asFieldInstruction().getField());
+ if (field == null || field.isStatic()) {
+ return user; // Not eligible.
}
- return user; // Not eligible.
+ continue;
}
if (user.isInvokeMethod()) {
@@ -393,7 +407,7 @@
methodCallsOnInstance.clear();
extraMethodCalls.clear();
unusedArguments.clear();
- estimatedCombinedSizeForInlining = 0;
+ receivers.reset();
// Repeat user analysis
InstructionOrPhi ineligibleUser = areInstanceUsersEligible(defaultOracle);
@@ -405,6 +419,7 @@
// methods (which is bad for memory), or we would need to analyze the called methods before
// inlining them. The latter could be good solution, since we are going to build IR for the
// methods that need to be inlined anyway.
+ assert appView.options().testing.allowClassInlinerGracefulExit;
return true;
}
assert extraMethodCalls.isEmpty()
@@ -432,7 +447,7 @@
block.listIterator(code, invoke).add(nullValue);
assert nullValue.getBlock() == block;
- int argIndex = unusedArgument.getSecond() + (invoke.isInvokeMethodWithReceiver() ? 1 : 0);
+ int argIndex = unusedArgument.getSecond();
invoke.replaceValue(argIndex, nullValue.outValue());
}
unusedArguments.clear();
@@ -453,10 +468,44 @@
if (methodCallsOnInstance.isEmpty()) {
return false;
}
+
assert methodCallsOnInstance.keySet().stream()
.map(InvokeMethodWithReceiver::getReceiver)
- .allMatch(receivers::contains);
+ .allMatch(receivers::isReceiverAlias);
+
inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
+
+ // In case we are class inlining an object allocation that does not inherit directly from
+ // java.lang.Object, we need keep force inlining the constructor until we reach
+ // java.lang.Object.<init>().
+ if (root.isNewInstance()) {
+ do {
+ methodCallsOnInstance.clear();
+ for (Instruction instruction : eligibleInstance.uniqueUsers()) {
+ if (instruction.isInvokeDirect()) {
+ InvokeDirect invoke = instruction.asInvokeDirect();
+ Value receiver = invoke.getReceiver();
+ if (receiver == eligibleInstance) {
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (appView.dexItemFactory().isConstructor(invokedMethod)
+ && invokedMethod != appView.dexItemFactory().objectMethods.constructor) {
+ methodCallsOnInstance.put(
+ invoke,
+ new InliningInfo(
+ appView.definitionFor(invokedMethod), root.asNewInstance().clazz));
+ break;
+ }
+ } else {
+ assert receiver.getAliasedValue() != eligibleInstance;
+ }
+ }
+ }
+ if (!methodCallsOnInstance.isEmpty()) {
+ inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
+ }
+ } while (!methodCallsOnInstance.isEmpty());
+ }
+
return true;
}
@@ -468,7 +517,7 @@
Assume<?> assumeInstruction = user.asAssume();
Value src = assumeInstruction.src();
Value dest = assumeInstruction.outValue();
- assert receivers.contains(dest);
+ assert receivers.isReceiverAlias(dest);
assert !dest.hasPhiUsers();
dest.replaceUsers(src);
removeInstruction(user);
@@ -481,13 +530,14 @@
private void removeMiscUsages(IRCode code) {
boolean needToRemoveUnreachableBlocks = false;
for (Instruction user : eligibleInstance.uniqueUsers()) {
- // Remove the call to superclass constructor.
- if (root.isNewInstance()
- && user.isInvokeDirect()
- && appView.dexItemFactory().isConstructor(user.asInvokeDirect().getInvokedMethod())
- && user.asInvokeDirect().getInvokedMethod().holder == eligibleClassDefinition.superType) {
- removeInstruction(user);
- continue;
+ // Remove the call to java.lang.Object.<init>().
+ if (user.isInvokeDirect()) {
+ InvokeDirect invoke = user.asInvokeDirect();
+ if (root.isNewInstance()
+ && invoke.getInvokedMethod() == appView.dexItemFactory().objectMethods.constructor) {
+ removeInstruction(invoke);
+ continue;
+ }
}
if (user.isIf()) {
@@ -601,7 +651,10 @@
+ "` after field reads removed: "
+ user);
}
- if (user.asInstancePut().getField().holder != eligibleClass) {
+ InstancePut instancePut = user.asInstancePut();
+ DexEncodedField field =
+ appView.appInfo().resolveFieldOn(eligibleClassDefinition, instancePut.getField());
+ if (field == null) {
throw new Unreachable(
"Unexpected field write left in method `"
+ method.method.toSourceString()
@@ -618,11 +671,18 @@
assert isEligibleSingleTarget(singleTarget);
// Must be a constructor called on the receiver.
- if (ListUtils.lastIndexMatching(
- invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) != 0) {
+ if (!receivers.isDefiniteReceiverAlias(invoke.getReceiver())) {
return null;
}
+ // None of the subsequent arguments may be an alias of the receiver.
+ List<Value> inValues = invoke.inValues();
+ for (int i = 1; i < inValues.size(); i++) {
+ if (!receivers.addIllegalReceiverAlias(inValues.get(i))) {
+ return null;
+ }
+ }
+
// Must be a constructor of the exact same class.
DexMethod init = invoke.getInvokedMethod();
if (init.holder != eligibleClass) {
@@ -639,7 +699,7 @@
if (isDesugaredLambda) {
// Lambda desugaring synthesizes eligible constructors.
- markSizeForInlining(singleTarget);
+ markSizeForInlining(invoke, singleTarget);
return new InliningInfo(singleTarget, eligibleClass);
}
@@ -681,10 +741,10 @@
// other invocations. In that case, we should add all indirect users of the out value to ensure
// they can also be inlined.
private boolean isEligibleInvokeWithAllUsersAsReceivers(
- ClassInlinerEligibility eligibility,
+ ClassInlinerEligibilityInfo eligibility,
InvokeMethodWithReceiver invoke,
Set<Instruction> indirectUsers) {
- if (!eligibility.returnsReceiver) {
+ if (eligibility.returnsReceiver.isFalse()) {
return true;
}
@@ -700,9 +760,13 @@
return false;
}
- // Since the invoke-instruction may return the receiver, the out-value may be an alias of the
- // receiver.
- receivers.add(outValue);
+ // Add the out-value as a definite-alias if the invoke instruction is guaranteed to return the
+ // receiver. Otherwise, the out-value may be an alias of the receiver, and it is added to the
+ // may-alias set.
+ AliasKind kind = eligibility.returnsReceiver.isTrue() ? AliasKind.DEFINITE : AliasKind.MAYBE;
+ if (!receivers.addReceiverAlias(outValue, kind)) {
+ return false;
+ }
Set<Instruction> currentUsers = outValue.uniqueUsers();
while (!currentUsers.isEmpty()) {
@@ -713,24 +777,29 @@
if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
return false;
}
- receivers.add(outValueAlias);
+ if (!receivers.addReceiverAlias(outValueAlias, kind)) {
+ return false;
+ }
indirectOutValueUsers.addAll(outValueAlias.uniqueUsers());
continue;
}
- if (!instruction.isInvokeMethodWithReceiver()) {
- return false;
- }
- InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
- if (user.getReceiver().getAliasedValue() != outValue) {
- return false;
- }
- for (int i = 1; i < user.inValues().size(); i++) {
- if (user.inValues().get(i).getAliasedValue() == outValue) {
+
+ if (instruction.isInvokeMethodWithReceiver()) {
+ InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
+ if (user.getReceiver().getAliasedValue() != outValue) {
return false;
}
+ for (int i = 1; i < user.inValues().size(); i++) {
+ if (user.inValues().get(i).getAliasedValue() == outValue) {
+ return false;
+ }
+ }
+ indirectUsers.add(user);
+ continue;
}
+
+ return false;
}
- indirectUsers.addAll(currentUsers);
currentUsers = indirectOutValueUsers;
}
@@ -743,10 +812,15 @@
Set<Instruction> indirectUsers,
Supplier<InliningOracle> defaultOracle) {
assert isEligibleSingleTarget(singleTarget);
- if (ListUtils.lastIndexMatching(
- invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) > 0) {
- return null; // Instance passed as an argument.
+
+ // None of the none-receiver arguments may be an alias of the receiver.
+ List<Value> inValues = invoke.inValues();
+ for (int i = 1; i < inValues.size(); i++) {
+ if (!receivers.addIllegalReceiverAlias(inValues.get(i))) {
+ return null;
+ }
}
+
// TODO(b/141719453): Should not constrain library overrides if all instantiations are inlined.
if (singleTarget.isLibraryMethodOverride().isTrue()) {
InliningOracle inliningOracle = defaultOracle.get();
@@ -756,7 +830,7 @@
}
}
return isEligibleVirtualMethodCall(
- !invoke.getBlock().hasCatchHandlers(),
+ invoke,
invoke.getInvokedMethod(),
singleTarget,
eligibility -> isEligibleInvokeWithAllUsersAsReceivers(eligibility, invoke, indirectUsers));
@@ -766,16 +840,16 @@
DexEncodedMethod singleTarget = eligibleClassDefinition.lookupVirtualMethod(callee);
if (isEligibleSingleTarget(singleTarget)) {
return isEligibleVirtualMethodCall(
- false, callee, singleTarget, eligibility -> !eligibility.returnsReceiver);
+ null, callee, singleTarget, eligibility -> eligibility.returnsReceiver.isFalse());
}
return null;
}
private InliningInfo isEligibleVirtualMethodCall(
- boolean allowMethodsWithoutNormalReturns,
+ InvokeMethodWithReceiver invoke,
DexMethod callee,
DexEncodedMethod singleTarget,
- Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
+ Predicate<ClassInlinerEligibilityInfo> eligibilityAcceptanceCheck) {
assert isEligibleSingleTarget(singleTarget);
// We should not inline a method if the invocation has type interface or virtual and the
@@ -794,13 +868,13 @@
}
if (isDesugaredLambda && !singleTarget.accessFlags.isBridge()) {
- markSizeForInlining(singleTarget);
+ markSizeForInlining(invoke, singleTarget);
return new InliningInfo(singleTarget, eligibleClass);
}
MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
- ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
+ ClassInlinerEligibilityInfo eligibility = optimizationInfo.getClassInlinerEligibility();
if (eligibility == null) {
return null;
}
@@ -811,7 +885,7 @@
return null;
}
- markSizeForInlining(singleTarget);
+ markSizeForInlining(invoke, singleTarget);
return new InliningInfo(singleTarget, eligibleClass);
}
@@ -820,10 +894,11 @@
&& appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
return false;
}
- if (invoke.isInvokeMethodWithReceiver()
- && invoke.asInvokeMethodWithReceiver().getReceiver().getAliasedValue()
- == eligibleInstance) {
- return false;
+ if (invoke.isInvokeMethodWithReceiver()) {
+ Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
+ if (!receivers.addIllegalReceiverAlias(receiver)) {
+ return false;
+ }
}
if (invoke.isInvokeSuper()) {
return false;
@@ -863,13 +938,13 @@
// If we got here with invocation on receiver the user is ineligible.
if (invoke.isInvokeMethodWithReceiver()) {
- if (arguments.get(0).getAliasedValue() == eligibleInstance) {
+ InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
+ Value receiver = invokeMethodWithReceiver.getReceiver();
+ if (!receivers.addIllegalReceiverAlias(receiver)) {
return false;
}
// TODO(b/124842076) Extend this check to use checksNullReceiverBeforeAnySideEffect.
- InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
- Value receiver = invokeMethodWithReceiver.getReceiver();
if (receiver.getTypeLattice().isNullable()) {
return false;
}
@@ -883,8 +958,11 @@
}
for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
- Value argument = arguments.get(argIndex);
- if (argument == eligibleInstance && optimizationInfo.getParameterUsages(argIndex).notUsed()) {
+ Value argument = arguments.get(argIndex).getAliasedValue();
+ ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex);
+ if (receivers.isDefiniteReceiverAlias(argument)
+ && parameterUsage != null
+ && parameterUsage.notUsed()) {
// Reference can be removed since it's not used.
unusedArguments.add(new Pair<>(invoke, argIndex));
}
@@ -893,7 +971,7 @@
extraMethodCalls.put(invoke, new InliningInfo(singleTarget, null));
// Looks good.
- markSizeForInlining(singleTarget);
+ markSizeForInlining(invoke, singleTarget);
return true;
}
@@ -904,16 +982,19 @@
Supplier<InliningOracle> defaultOracle) {
// Go through all arguments, see if all usages of eligibleInstance are good.
for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
- Value argument = arguments.get(argIndex).getAliasedValue();
- if (argument != eligibleInstance) {
- continue; // Nothing to worry about.
- }
-
- // Have parameter usage info?
MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex);
- if (!isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)) {
- return false;
+
+ Value argument = arguments.get(argIndex);
+ if (receivers.isReceiverAlias(argument)) {
+ // Have parameter usage info?
+ if (!isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)) {
+ return false;
+ }
+ } else {
+ // Nothing to worry about, unless `argument` becomes an alias of the receiver later.
+ receivers.addDeferredAliasValidityCheck(
+ argument, () -> isEligibleParameterUsage(parameterUsage, invoke, defaultOracle));
}
}
return true;
@@ -1015,9 +1096,14 @@
kotlinInfo.asSyntheticClass().isLambda();
}
- private void markSizeForInlining(DexEncodedMethod inlinee) {
+ private void markSizeForInlining(InvokeMethod invoke, DexEncodedMethod inlinee) {
+ assert !isProcessedConcurrently.test(inlinee);
if (!exemptFromInstructionLimit(inlinee)) {
- estimatedCombinedSizeForInlining += inlinee.getCode().estimatedSizeForInlining();
+ if (invoke != null) {
+ directInlinees.put(invoke, inlinee);
+ } else {
+ indirectInlinees.add(inlinee);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index c38c0ed..702074a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.util.List;
@@ -99,6 +100,11 @@
public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
TypeLatticeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
for (int i = 0; i < size; i++) {
+ ParameterUsage parameterUsage = encodedMethod.getOptimizationInfo().getParameterUsages(i);
+ // If the parameter is not used, passing accurate argument info doesn't matter.
+ if (parameterUsage != null && parameterUsage.notUsed()) {
+ continue;
+ }
AbstractValue abstractValue = getAbstractArgumentValue(i);
if (abstractValue.isNonTrivial()) {
assert appView.options().enablePropagationOfConstantsAtCallSites;
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 e6d0589..7ecbf18 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
@@ -4,12 +4,12 @@
package com.android.tools.r8.ir.optimize.info;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
@@ -32,7 +32,7 @@
static boolean DOES_NOT_USE_IDENTIFIER_NAME_STRING = false;
static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
- static ClassInlinerEligibility UNKNOWN_CLASS_INLINER_ELIGIBILITY = null;
+ static ClassInlinerEligibilityInfo UNKNOWN_CLASS_INLINER_ELIGIBILITY = null;
static boolean UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS = false;
static ParameterUsagesInfo UNKNOWN_PARAMETER_USAGE_INFO = null;
static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
@@ -140,7 +140,7 @@
}
@Override
- public ClassInlinerEligibility getClassInlinerEligibility() {
+ public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
return UNKNOWN_CLASS_INLINER_ELIGIBILITY;
}
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 26aab1b..d9c4083 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
@@ -4,11 +4,11 @@
package com.android.tools.r8.ir.optimize.info;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -55,7 +55,7 @@
boolean neverReturnsNormally();
- ClassInlinerEligibility getClassInlinerEligibility();
+ ClassInlinerEligibilityInfo getClassInlinerEligibility();
Set<DexType> getInitializedClassesOnNormalExit();
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 13844a6..ee49c97 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
@@ -10,7 +10,6 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -38,6 +37,8 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
@@ -123,7 +124,6 @@
return;
}
- boolean receiverUsedAsReturnValue = false;
boolean seenSuperInitCall = false;
for (Instruction insn : receiver.aliasedUsers()) {
if (insn.isAssume()) {
@@ -134,11 +134,6 @@
continue;
}
- if (insn.isReturn()) {
- receiverUsedAsReturnValue = true;
- continue;
- }
-
if (insn.isInstanceGet() || insn.isInstancePut()) {
if (insn.isInstancePut()) {
InstancePut instancePutInstruction = insn.asInstancePut();
@@ -176,16 +171,23 @@
return;
}
+ if (insn.isReturn()) {
+ continue;
+ }
+
// Other receiver usages make the method not eligible.
return;
}
+
if (instanceInitializer && !seenSuperInitCall) {
// Call to super constructor not found?
return;
}
feedback.setClassInlinerEligibility(
- method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
+ method,
+ new ClassInlinerEligibilityInfo(
+ new ClassInlinerReceiverAnalysis(appView, method, code).computeReturnsReceiver()));
}
private void identifyParameterUsages(
@@ -442,7 +444,7 @@
if (insn.isInstancePut()) {
InstancePut instancePut = insn.asInstancePut();
- DexEncodedField field = clazz.lookupInstanceField(instancePut.getField());
+ DexEncodedField field = appView.appInfo().resolveFieldOn(clazz, instancePut.getField());
if (field == null
|| instancePut.object() != receiver
|| (instancePut.value() != receiver && !instancePut.value().isArgument())) {
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 2f9c55b..a0828e9 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
@@ -7,12 +7,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.IteratorUtils;
@@ -228,7 +228,7 @@
@Override
public synchronized void setClassInlinerEligibility(
- DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+ DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
getMethodOptimizationInfoForUpdating(method).setClassInlinerEligibility(eligibility);
}
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 1be0c5c..e106dc8 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
@@ -7,12 +7,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.BitSet;
@@ -109,8 +109,7 @@
@Override
public void setClassInlinerEligibility(
- DexEncodedMethod method, ClassInlinerEligibility eligibility) {
- }
+ DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {}
@Override
public void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {}
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 1d1dea5..0178a2d 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
@@ -7,12 +7,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.BitSet;
@@ -156,7 +156,7 @@
@Override
public void setClassInlinerEligibility(
- DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+ DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
// Ignored.
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
index e41989a..ef87dcf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
@@ -118,8 +118,8 @@
}
public boolean notUsed() {
- return ifZeroTest == null
- && callsReceiver == null
+ return (ifZeroTest == null || ifZeroTest.isEmpty())
+ && (callsReceiver == null || callsReceiver.isEmpty())
&& !hasFieldAssignment
&& !hasFieldRead
&& !isAssignedToField
@@ -147,8 +147,9 @@
// Returns false if the instruction is not supported.
public boolean note(Instruction instruction) {
if (instruction.isAssume()) {
- // Keep examining other users, but the param usage builder should consider aliased users.
- return true;
+ // Keep examining other users if there are no phi users, but the param usage builder should
+ // consider aliased users.
+ return !instruction.outValue().hasPhiUsers();
}
if (instruction.isIf()) {
return note(instruction.asIf());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index c5a12c2..74dd886 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -6,11 +6,11 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
@@ -49,7 +49,7 @@
DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
// Stores information about instance methods and constructors for
// class inliner, null value indicates that the method is not eligible.
- private ClassInlinerEligibility classInlinerEligibility =
+ private ClassInlinerEligibilityInfo classInlinerEligibility =
DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
private InitializerInfo initializerInfo = null;
private boolean initializerEnablingJavaAssertions =
@@ -235,7 +235,7 @@
}
@Override
- public ClassInlinerEligibility getClassInlinerEligibility() {
+ public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
return classInlinerEligibility;
}
@@ -300,7 +300,7 @@
this.reachabilitySensitive = reachabilitySensitive;
}
- void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
+ void setClassInlinerEligibility(ClassInlinerEligibilityInfo eligibility) {
this.classInlinerEligibility = eligibility;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index 580e1b2..b1408dd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -38,6 +38,12 @@
return method.buildInliningIR(context, appView, valueNumberGenerator, position, origin);
}
+ public IRCode getAndCacheInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
+ IRCode inliningIR = getInliningIR(invoke, method);
+ cacheInliningIR(invoke, inliningIR);
+ return inliningIR;
+ }
+
public void cacheInliningIR(InvokeMethod invoke, IRCode code) {
IRCode existing = cache.put(invoke, code);
assert existing == null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index 5c3e91c..ae5e066 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -80,10 +80,6 @@
}
}
- public final boolean allLambdas(Predicate<LambdaInfo> predicate) {
- return !anyLambda(lambda -> !predicate.test(lambda));
- }
-
public final boolean anyLambda(Predicate<LambdaInfo> predicate) {
assert verifyLambdaIds(false);
for (LambdaInfo info : lambdas.values()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index a165998..3bb09ce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -15,18 +15,23 @@
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.classmerging.HorizontallyMergedLambdaClasses;
import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
+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.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -40,6 +45,8 @@
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -84,7 +91,8 @@
private abstract static class Mode {
- void rewriteCode(DexEncodedMethod method, IRCode code, DexEncodedMethod context) {}
+ void rewriteCode(
+ DexEncodedMethod method, IRCode code, Inliner inliner, DexEncodedMethod context) {}
void analyzeCode(DexEncodedMethod method, IRCode code) {}
}
@@ -99,22 +107,50 @@
private class ApplyMode extends Mode {
- private final Set<DexType> lambdaGroupsClasses;
+ private final Map<DexProgramClass, LambdaGroup> lambdaGroups;
private final LambdaMergerOptimizationInfoFixer optimizationInfoFixer;
ApplyMode(
- Set<DexType> lambdaGroupTypes, LambdaMergerOptimizationInfoFixer optimizationInfoFixer) {
- this.lambdaGroupsClasses = lambdaGroupTypes;
+ Map<DexProgramClass, LambdaGroup> lambdaGroups,
+ LambdaMergerOptimizationInfoFixer optimizationInfoFixer) {
+ this.lambdaGroups = lambdaGroups;
this.optimizationInfoFixer = optimizationInfoFixer;
}
@Override
- void rewriteCode(DexEncodedMethod method, IRCode code, DexEncodedMethod context) {
- if (lambdaGroupsClasses.contains(method.method.holder)) {
- // Don't rewrite the methods that we have synthesized for the lambda group classes.
+ void rewriteCode(
+ DexEncodedMethod method, IRCode code, Inliner inliner, DexEncodedMethod context) {
+ DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+ assert clazz != null;
+
+ LambdaGroup lambdaGroup = lambdaGroups.get(clazz);
+ if (lambdaGroup == null) {
+ // Only rewrite the methods that have not been synthesized for the lambda group classes.
+ new ApplyStrategy(method, code, context, optimizationInfoFixer).processCode();
return;
}
- new ApplyStrategy(method, code, context, optimizationInfoFixer).processCode();
+
+ if (method.isInitializer()) {
+ // Should not require rewriting.
+ return;
+ }
+
+ assert method.isVirtualMethod();
+ assert context == null;
+
+ Map<InvokeVirtual, InliningInfo> invokesToInline = new IdentityHashMap<>();
+ for (InvokeVirtual invoke : code.<InvokeVirtual>instructions(Instruction::isInvokeVirtual)) {
+ DexType holder = invoke.getInvokedMethod().holder;
+ if (lambdaGroup.containsLambda(holder)) {
+ DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.method.holder);
+ assert singleTarget != null;
+ invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.method.holder));
+ }
+ }
+
+ assert invokesToInline.size() > 1;
+
+ inliner.performForcedInlining(method, code, invokesToInline);
}
}
@@ -143,7 +179,6 @@
private final Set<DexEncodedMethod> methodsToReprocess = Sets.newIdentityHashSet();
private final AppView<AppInfoWithLiveness> appView;
- private final DexItemFactory factory;
private final Kotlin kotlin;
private final DiagnosticsHandler reporter;
@@ -155,17 +190,20 @@
private final LambdaTypeVisitor lambdaChecker;
public LambdaMerger(AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory factory = appView.dexItemFactory();
this.appView = appView;
- this.factory = appView.dexItemFactory();
this.kotlin = factory.kotlin;
this.reporter = appView.options().reporter;
- this.lambdaInvalidator = new LambdaTypeVisitor(factory, this::isMergeableLambda,
- this::invalidateLambda);
- this.lambdaChecker = new LambdaTypeVisitor(factory, this::isMergeableLambda,
- type -> {
- throw new Unreachable("Unexpected lambda " + type.toSourceString());
- });
+ this.lambdaInvalidator =
+ new LambdaTypeVisitor(factory, this::isMergeableLambda, this::invalidateLambda);
+ this.lambdaChecker =
+ new LambdaTypeVisitor(
+ factory,
+ this::isMergeableLambda,
+ type -> {
+ throw new Unreachable("Unexpected lambda " + type.toSourceString());
+ });
}
private void invalidateLambda(DexType lambda) {
@@ -251,20 +289,20 @@
* no more invalid lambda class references.
* </ol>
*/
- public final void rewriteCode(DexEncodedMethod method, IRCode code) {
+ public final void rewriteCode(DexEncodedMethod method, IRCode code, Inliner inliner) {
if (mode != null) {
- mode.rewriteCode(method, code, null);
+ mode.rewriteCode(method, code, inliner, null);
}
}
/**
- * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode)}, but for rewriting code for inlining.
- * The {@param context} is the caller that {@param method} is being inlined into.
+ * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode, Inliner)}, but for rewriting code for
+ * inlining. The {@param context} is the caller that {@param method} is being inlined into.
*/
public final void rewriteCodeForInlining(
DexEncodedMethod method, IRCode code, DexEncodedMethod context) {
if (mode != null) {
- mode.rewriteCode(method, code, context);
+ mode.rewriteCode(method, code, null, context);
}
}
@@ -288,12 +326,7 @@
// Remove invalidated lambdas, compact groups to ensure
// sequential lambda ids, create group lambda classes.
- Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
-
- // Mark all the implementation methods for force inlining.
- for (LambdaGroup group : lambdaGroupsClasses.keySet()) {
- group.forEachLambda(info -> info.clazz.virtualMethods().forEach(feedback::markForceInline));
- }
+ BiMap<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
// Fixup optimization info to ensure that the optimization info does not refer to any merged
// lambdas.
@@ -302,9 +335,7 @@
feedback.fixupOptimizationInfos(appView, executorService, optimizationInfoFixer);
// Switch to APPLY strategy.
- Set<DexType> lambdaGroupTypes =
- lambdaGroupsClasses.values().stream().map(clazz -> clazz.type).collect(Collectors.toSet());
- this.mode = new ApplyMode(lambdaGroupTypes, optimizationInfoFixer);
+ this.mode = new ApplyMode(lambdaGroupsClasses.inverse(), optimizationInfoFixer);
// Add synthesized lambda group classes to the builder.
for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
@@ -323,23 +354,15 @@
encodedMethod -> encodedMethod.markProcessed(ConstraintWithTarget.NEVER));
}
- // Verify that all implementation methods are marked for force inlining (i.e., check that the
- // delayed optimization feedback has been flushed).
- assert lambdaGroupsClasses.keySet().stream()
- .allMatch(
- group ->
- group.allLambdas(
- lambda ->
- lambda.clazz.virtualMethods().stream()
- .map(DexEncodedMethod::getOptimizationInfo)
- .allMatch(MethodOptimizationInfo::forceInline)));
-
converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
// Rewrite lambda class references into lambda group class
// references inside methods from the processing queue.
rewriteLambdaReferences(converter, executorService, feedback);
this.mode = null;
+
+ appView.setHorizontallyMergedLambdaClasses(
+ new HorizontallyMergedLambdaClasses(lambdas.keySet()));
}
private void analyzeLambdaClassesStructure(ExecutorService service) throws ExecutionException {
@@ -365,7 +388,7 @@
ThreadUtils.awaitFutures(futures);
}
- private Map<LambdaGroup, DexProgramClass> finalizeLambdaGroups() {
+ private BiMap<LambdaGroup, DexProgramClass> finalizeLambdaGroups() {
for (DexType lambda : invalidatedLambdas) {
LambdaGroup group = lambdas.get(lambda);
assert group != null;
@@ -378,7 +401,7 @@
removeTrivialLambdaGroups();
// Compact lambda groups, synthesize lambda group classes.
- Map<LambdaGroup, DexProgramClass> result = new LinkedHashMap<>();
+ BiMap<LambdaGroup, DexProgramClass> result = HashBiMap.create();
for (LambdaGroup group : groups.values()) {
assert !group.isTrivial() : "No trivial group is expected here.";
group.compact();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index b9ef1c9..a467cb2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.util.ArrayList;
@@ -57,7 +58,8 @@
private final ClassStaticizer classStaticizer;
private final IRConverter converter;
- private final Map<DexEncodedMethod, Collection<Consumer<IRCode>>> processingQueue =
+ // Optimization order matters, hence a collection that preserves orderings.
+ private final Map<DexEncodedMethod, ImmutableList.Builder<Consumer<IRCode>>> processingQueue =
new IdentityHashMap<>();
private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
@@ -75,7 +77,7 @@
this.converter = converter;
}
- final void run(OptimizationFeedback optimizationFeedback, ExecutorService executorService)
+ final void run(OptimizationFeedback feedback, ExecutorService executorService)
throws ExecutionException {
// Filter out candidates based on the information we collected while examining methods.
finalEligibilityCheck();
@@ -84,15 +86,20 @@
prepareCandidates();
// Enqueue all host class initializers (only remove instantiations).
- enqueueMethodsWithCodeOptimization(
- hostClassInits.keySet(), this::removeCandidateInstantiation);
+ enqueueMethodsWithCodeOptimizations(
+ hostClassInits.keySet(),
+ optimizations ->
+ optimizations
+ .add(this::removeCandidateInstantiation)
+ .add(this::insertAssumeInstructions)
+ .add(collectOptimizationInfo(feedback)));
// Enqueue instance methods to be staticized (only remove references to 'this').
- enqueueMethodsWithCodeOptimization(
- methodsToBeStaticized, this::removeReferencesToThis);
+ enqueueMethodsWithCodeOptimizations(
+ methodsToBeStaticized, optimizations -> optimizations.add(this::removeReferencesToThis));
// Process queued methods with associated optimizations
- processMethodsConcurrently(optimizationFeedback, executorService);
+ processMethodsConcurrently(feedback, executorService);
// TODO(b/140767158): Merge the remaining part below.
// Convert instance methods into static methods with an extra parameter.
@@ -103,10 +110,16 @@
// a result of staticizing.)
methods.addAll(referencingExtraMethods);
methods.addAll(hostClassInits.keySet());
- enqueueMethodsWithCodeOptimization(methods, this::rewriteReferences);
+ enqueueMethodsWithCodeOptimizations(
+ methods,
+ optimizations ->
+ optimizations
+ .add(this::rewriteReferences)
+ .add(this::insertAssumeInstructions)
+ .add(collectOptimizationInfo(feedback)));
// Process queued methods with associated optimizations
- processMethodsConcurrently(optimizationFeedback, executorService);
+ processMethodsConcurrently(feedback, executorService);
}
private void finalEligibilityCheck() {
@@ -249,15 +262,11 @@
referencingExtraMethods.removeAll(removedInstanceMethods);
}
- private void enqueueMethodsWithCodeOptimization(
- Iterable<DexEncodedMethod> methods, Consumer<IRCode> optimization) {
+ private void enqueueMethodsWithCodeOptimizations(
+ Iterable<DexEncodedMethod> methods,
+ Consumer<ImmutableList.Builder<Consumer<IRCode>>> extension) {
for (DexEncodedMethod method : methods) {
- processingQueue
- .computeIfAbsent(
- method,
- // Optimization order might matter, hence a collection that preserves orderings.
- k -> new ArrayList<>())
- .add(optimization);
+ extension.accept(processingQueue.computeIfAbsent(method, ignore -> ImmutableList.builder()));
}
}
@@ -275,7 +284,7 @@
OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
ThreadUtils.processItems(
processingQueue.keySet(),
- method -> forEachMethod(method, processingQueue.get(method), feedback),
+ method -> forEachMethod(method, processingQueue.get(method).build(), feedback),
executorService);
// TODO(b/140767158): No need to clear if we can do every thing in one go.
processingQueue.clear();
@@ -289,12 +298,18 @@
Origin origin = appView.appInfo().originFor(method.method.holder);
IRCode code = method.buildIR(appView, origin);
codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code));
- CodeRewriter.insertAssumeInstructions(code, converter.assumers);
- converter.collectOptimizationInfo(code, feedback);
CodeRewriter.removeAssumeInstructions(appView, code);
converter.finalizeIR(method, code, feedback);
}
+ private void insertAssumeInstructions(IRCode code) {
+ CodeRewriter.insertAssumeInstructions(code, converter.assumers);
+ }
+
+ private Consumer<IRCode> collectOptimizationInfo(OptimizationFeedback feedback) {
+ return code -> converter.collectOptimizationInfo(code, feedback);
+ }
+
private void removeCandidateInstantiation(IRCode code) {
CandidateInfo candidateInfo = hostClassInits.get(code.method);
assert candidateInfo != null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index a83c407..acb6c55 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -11,7 +11,9 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ListIterator;
import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmType;
import kotlinx.metadata.KmTypeVisitor;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -40,23 +42,36 @@
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- kmClass.getSupertypes().removeIf(
- kmType -> {
- Box<Boolean> isLive = new Box<>(false);
- kmType.accept(new KmTypeVisitor() {
- @Override
- public void visitClass(String name) {
- String descriptor = DescriptorUtils.javaTypeToDescriptorIfValidJavaType(name);
- if (descriptor != null) {
- DexType type = appView.dexItemFactory().createType(name);
- DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
- isLive.set(appView.appInfo().isLiveProgramType(renamedType));
- }
- }
- });
- return !isLive.get();
+ ListIterator<KmType> superTypeIterator = kmClass.getSupertypes().listIterator();
+ while (superTypeIterator.hasNext()) {
+ KmType kmType = superTypeIterator.next();
+ Box<Boolean> isLive = new Box<>(false);
+ Box<DexType> renamed = new Box<>(null);
+ kmType.accept(new KmTypeVisitor() {
+ @Override
+ public void visitClass(String name) {
+ String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(name);
+ DexType type = appView.dexItemFactory().createType(descriptor);
+ isLive.set(appView.appInfo().isLiveProgramType(type));
+ DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+ if (renamedType != type) {
+ renamed.set(renamedType);
+ }
}
- );
+ });
+ if (!isLive.get()) {
+ superTypeIterator.remove();
+ continue;
+ }
+ if (renamed.get() != null) {
+ // TODO(b/70169921): need a general util to convert the current clazz's access flag.
+ KmType renamedKmType = new KmType(kmType.getFlags());
+ renamedKmType.visitClass(
+ DescriptorUtils.descriptorToInternalName(renamed.get().toDescriptorString()));
+ superTypeIterator.remove();
+ superTypeIterator.add(renamedKmType);
+ }
+ }
}
@Override
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 5ccf3e8..226a5f9 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
@@ -30,9 +31,10 @@
public static class Builder extends ClassNaming.Builder {
private final String originalName;
private final String renamedName;
- private final Map<MethodSignature, MemberNaming> methodMembers = new HashMap<>();
- private final Map<FieldSignature, MemberNaming> fieldMembers = new HashMap<>();
- private final Map<String, List<MappedRange>> mappedRangesByName = new HashMap<>();
+ private final Map<MethodSignature, MemberNaming> methodMembers = Maps.newHashMap();
+ private final Map<FieldSignature, MemberNaming> fieldMembers = Maps.newHashMap();
+ private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap();
+ private final Map<String, List<MemberNaming>> mappedNamingsByName = Maps.newHashMap();
private Builder(String renamedName, String originalName) {
this.originalName = originalName;
@@ -46,6 +48,9 @@
} else {
fieldMembers.put((FieldSignature) entry.getRenamedSignature(), entry);
}
+ mappedNamingsByName
+ .computeIfAbsent(entry.getRenamedName(), m -> new ArrayList<>())
+ .add(entry);
return this;
}
@@ -63,7 +68,7 @@
}
return new ClassNamingForNameMapper(
- renamedName, originalName, methodMembers, fieldMembers, map);
+ renamedName, originalName, methodMembers, fieldMembers, map, mappedNamingsByName);
}
/** The parameters are forwarded to MappedRange constructor, see explanation there. */
@@ -192,17 +197,21 @@
/** Map of renamed name -> MappedRangesOfName */
public final Map<String, MappedRangesOfName> mappedRangesByRenamedName;
+ public final Map<String, List<MemberNaming>> mappedNamingsByName;
+
private ClassNamingForNameMapper(
String renamedName,
String originalName,
Map<MethodSignature, MemberNaming> methodMembers,
Map<FieldSignature, MemberNaming> fieldMembers,
- Map<String, MappedRangesOfName> mappedRangesByRenamedName) {
+ Map<String, MappedRangesOfName> mappedRangesByRenamedName,
+ Map<String, List<MemberNaming>> mappedNamingsByName) {
this.renamedName = renamedName;
this.originalName = originalName;
this.methodMembers = ImmutableMap.copyOf(methodMembers);
this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
this.mappedRangesByRenamedName = mappedRangesByRenamedName;
+ this.mappedNamingsByName = mappedNamingsByName;
}
@Override
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 18f8459..054917e 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -87,6 +87,10 @@
return signature.kind() == SignatureKind.METHOD;
}
+ public boolean isFieldNaming() {
+ return signature.kind() == SignatureKind.FIELD;
+ }
+
public Position getPosition() {
return position;
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 011a7ba..0cee87f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -91,7 +91,7 @@
if (minApiLevel != null) {
builder.append("# " + MARKER_KEY_MIN_API + ": " + minApiLevel + "\n");
}
- if (Version.isDev()) {
+ if (Version.isDevelopmentVersion()) {
builder.append(
"# " + MARKER_KEY_COMPILER_HASH + ": " + VersionProperties.INSTANCE.getSha() + "\n");
}
diff --git a/src/main/java/com/android/tools/r8/references/ArrayReference.java b/src/main/java/com/android/tools/r8/references/ArrayReference.java
index 96cb926..f95a60c 100644
--- a/src/main/java/com/android/tools/r8/references/ArrayReference.java
+++ b/src/main/java/com/android/tools/r8/references/ArrayReference.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.Keep;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.DescriptorUtils;
/** Reference to an array type. */
@Keep
@@ -34,6 +35,13 @@
throw new Unreachable("Invalid array type descriptor: " + descriptor);
}
+ static ArrayReference fromBaseType(TypeReference baseType, int dimensions) {
+ return new ArrayReference(
+ dimensions,
+ baseType,
+ DescriptorUtils.toArrayDescriptor(dimensions, baseType.getDescriptor()));
+ }
+
public int getDimensions() {
return dimensions;
}
diff --git a/src/main/java/com/android/tools/r8/references/ClassReference.java b/src/main/java/com/android/tools/r8/references/ClassReference.java
index 1159ec3..9c0f479 100644
--- a/src/main/java/com/android/tools/r8/references/ClassReference.java
+++ b/src/main/java/com/android/tools/r8/references/ClassReference.java
@@ -30,6 +30,11 @@
}
@Override
+ public ClassReference asClass() {
+ return this;
+ }
+
+ @Override
public String getDescriptor() {
return descriptor;
}
diff --git a/src/main/java/com/android/tools/r8/references/PrimitiveReference.java b/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
index 66c1ae7..eb4ae72 100644
--- a/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
+++ b/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
@@ -105,6 +105,11 @@
}
@Override
+ public PrimitiveReference asPrimitive() {
+ return this;
+ }
+
+ @Override
public abstract String getDescriptor();
@Override
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index 8a3d278..94d4ea0 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -118,6 +118,16 @@
return getInstance().arrays.computeIfAbsent(descriptor, ArrayReference::fromDescriptor);
}
+ /** Get an array reference from a base type and dimensions. */
+ public static ArrayReference array(TypeReference baseType, int dimensions) {
+ String arrayDescriptor =
+ DescriptorUtils.toArrayDescriptor(dimensions, baseType.getDescriptor());
+ return getInstance()
+ .arrays
+ .computeIfAbsent(
+ arrayDescriptor, descriptor -> ArrayReference.fromBaseType(baseType, dimensions));
+ }
+
/** Get a method reference from its full reference specification. */
public static MethodReference method(
ClassReference holderClass,
diff --git a/src/main/java/com/android/tools/r8/references/TypeReference.java b/src/main/java/com/android/tools/r8/references/TypeReference.java
index 44fb54a..bfef3f6 100644
--- a/src/main/java/com/android/tools/r8/references/TypeReference.java
+++ b/src/main/java/com/android/tools/r8/references/TypeReference.java
@@ -31,6 +31,33 @@
return false;
}
+ /**
+ * Return a non-null ClassTypeReference if this type is ClassTypeReference
+ *
+ * @return this with static type of ClassTypeReference
+ */
+ default ClassReference asClass() {
+ return null;
+ }
+
+ /**
+ * Return a non-null ArrayReference if this type is ArrayReference
+ *
+ * @return this with static type of ArrayReference
+ */
+ default ArrayReference asArray() {
+ return null;
+ }
+
+ /**
+ * Return a non-null PrimitiveReference if this type is PrimitiveReference
+ *
+ * @return this with static type of PrimitiveReference
+ */
+ default PrimitiveReference asPrimitive() {
+ return null;
+ }
+
default String getTypeName() {
return DescriptorUtils.descriptorToJavaType(getDescriptor());
}
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 f5b0de8..d004c90 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -567,7 +567,7 @@
public boolean isLiveProgramType(DexType type) {
DexClass clazz = definitionFor(type);
- return clazz.isProgramClass() && isLiveProgramClass(clazz.asProgramClass());
+ return clazz != null && clazz.isProgramClass() && isLiveProgramClass(clazz.asProgramClass());
}
public boolean isNonProgramTypeOrLiveProgramType(DexType type) {
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 0652a33..cee2d66 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -395,7 +395,7 @@
} else {
String unknownOption = acceptString();
String devMessage = "";
- if (Version.isDev()
+ if (Version.isDevelopmentVersion()
&& unknownOption != null
&& (unknownOption.equals("forceinline") || unknownOption.equals("neverinline"))) {
devMessage = ", this option needs to be turned on explicitly if used for tests.";
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 af0efe5..e7a7fe5 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -289,7 +289,8 @@
// Final classes cannot be abstract, so we have to keep the method in that case.
// Also some other kinds of methods cannot be abstract, so keep them around.
boolean allowAbstract =
- clazz.accessFlags.isAbstract()
+ (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
+ || clazz.accessFlags.isAbstract())
&& !method.accessFlags.isFinal()
&& !method.accessFlags.isNative()
&& !method.accessFlags.isStrict()
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 f07499b..9f4b3e3 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
@@ -47,10 +48,8 @@
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.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -88,44 +87,6 @@
*/
public class VerticalClassMerger {
- public static class VerticallyMergedClasses {
-
- private final Map<DexType, DexType> mergedClasses;
- private final Map<DexType, List<DexType>> sources;
-
- private VerticallyMergedClasses(Map<DexType, DexType> mergedClasses) {
- Map<DexType, List<DexType>> sources = Maps.newIdentityHashMap();
- mergedClasses.forEach(
- (source, target) ->
- sources.computeIfAbsent(target, key -> new ArrayList<>()).add(source));
- this.mergedClasses = mergedClasses;
- this.sources = sources;
- }
-
- public List<DexType> getSourcesFor(DexType type) {
- return sources.getOrDefault(type, ImmutableList.of());
- }
-
- public DexType getTargetFor(DexType type) {
- assert mergedClasses.containsKey(type);
- return mergedClasses.get(type);
- }
-
- public boolean hasBeenMergedIntoSubtype(DexType type) {
- return mergedClasses.containsKey(type);
- }
-
- public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
- for (List<DexType> sourcesForTarget : sources.values()) {
- for (DexType source : sourcesForTarget) {
- assert appView.appInfo().wasPruned(source)
- : "Expected vertically merged class `" + source.toSourceString() + "` to be absent";
- }
- }
- return true;
- }
- }
-
private enum AbortReason {
ALREADY_MERGED,
ALWAYS_INLINE,
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 09fa0fe..c3916cb 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -96,6 +96,23 @@
}
/**
+ * Produces an array descriptor having the number of dimensions specified and the
+ * baseTypeDescriptor as base.
+ *
+ * @param dimensions number of dimensions
+ * @param baseTypeDescriptor the base type
+ * @return the descriptor string
+ */
+ public static String toArrayDescriptor(int dimensions, String baseTypeDescriptor) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < dimensions; i++) {
+ sb.append('[');
+ }
+ sb.append(baseTypeDescriptor);
+ return sb.toString();
+ }
+
+ /**
* Determine the given {@param typeName} is a valid jvms binary name or not (jvms 4.2.1).
*
* @param typeName the jvms binary name
@@ -177,13 +194,40 @@
classNameMapper == null ? clazz : classNameMapper.deobfuscateClassName(clazz);
return originalName;
case '[':
- return descriptorToJavaType(descriptor.substring(1, descriptor.length()), classNameMapper)
- + "[]";
+ return descriptorToJavaType(descriptor.substring(1), classNameMapper) + "[]";
default:
return primitiveDescriptorToJavaType(c);
}
}
+ public static boolean isPrimitiveDescriptor(String descriptor) {
+ if (descriptor.length() != 1) {
+ return false;
+ }
+ return isPrimitiveType(descriptor.charAt(0));
+ }
+
+ public static boolean isPrimitiveType(char c) {
+ return c == 'Z' || c == 'B' || c == 'S' || c == 'C' || c == 'I' || c == 'F' || c == 'J'
+ || c == 'D';
+ }
+
+ public static boolean isArrayDescriptor(String descriptor) {
+ if (descriptor.length() < 2) {
+ return false;
+ }
+ if (descriptor.charAt(0) == '[') {
+ return isDescriptor(descriptor.substring(1));
+ }
+ return false;
+ }
+
+ public static boolean isDescriptor(String descriptor) {
+ return isClassDescriptor(descriptor)
+ || isPrimitiveDescriptor(descriptor)
+ || isArrayDescriptor(descriptor);
+ }
+
public static String primitiveDescriptorToJavaType(char primitive) {
switch (primitive) {
case 'V':
@@ -300,6 +344,17 @@
}
/**
+ * Convert a fully qualified name of a classifier in Kotlin metadata to a descriptor.
+ * @param className "org/foo/bar/Baz.Nested"
+ * @return a class descriptor like "Lorg/foo/bar/Baz$Nested;"
+ */
+ public static String getDescriptorFromKotlinClassifier(String className) {
+ assert className != null;
+ assert !className.contains("[") : className;
+ return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';';
+ }
+
+ /**
* Get unqualified class name from its binary name.
*
* @param classBinaryName a class binary name i.e. "java/lang/Object" or "a/b/C$Inner"
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 3a0b969..dc998e2 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -202,7 +202,8 @@
public boolean enableDevirtualization = true;
public boolean enableNonNullTracking = true;
public boolean enableInlining =
- !Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
+ !Version.isDevelopmentVersion()
+ || System.getProperty("com.android.tools.r8.disableinlining") == null;
// TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
public boolean applyInliningToInlinee = false;
public int applyInliningToInlineeMaxDepth = 0;
@@ -293,7 +294,7 @@
if (!isGeneratingClassFiles()) {
marker.setMinApi(minApiLevel);
}
- if (Version.isDev()) {
+ if (Version.isDevelopmentVersion()) {
marker.setSha1(VersionProperties.INSTANCE.getSha());
}
return marker;
@@ -985,9 +986,11 @@
public boolean addCallEdgesForLibraryInvokes = false;
public boolean allowTypeErrors =
- !Version.isDev() || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
+ !Version.isDevelopmentVersion()
+ || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
public boolean allowInvokeErrors = false;
public boolean disableL8AnnotationRemoval = false;
+ public boolean allowClassInlinerGracefulExit = true;
public boolean allowUnusedProguardConfigurationRules = true;
public boolean reportUnusedProguardConfigurationRules = false;
public boolean alwaysUsePessimisticRegisterAllocation = false;
diff --git a/src/main/java/com/android/tools/r8/utils/OptionalBool.java b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
index 54029d5..abb048d 100644
--- a/src/main/java/com/android/tools/r8/utils/OptionalBool.java
+++ b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
@@ -62,4 +62,18 @@
public OptionalBool asOptionalBool() {
return this;
}
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ // Force all subtypes to implement toString().
+ @Override
+ public abstract String toString();
}
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 44a26be..265d333 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -309,4 +309,13 @@
}
return true;
}
+
+ public static char lastChar(String s) {
+ return charFromEnd(s, 0);
+ }
+
+ public static char charFromEnd(String s, int charsFromEnd) {
+ assert s.length() > charsFromEnd;
+ return s.charAt(s.length() - (charsFromEnd + 1));
+ }
}
diff --git a/src/main/keep.txt b/src/main/keep.txt
index 2748f63..d33b879 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -12,7 +12,13 @@
-keep public class com.android.tools.r8.compatdx.CompatDx { public static void main(java.lang.String[]); }
-keep public class com.android.tools.r8.dexfilemerger.DexFileMerger { public static void main(java.lang.String[]); }
-keep public class com.android.tools.r8.dexsplitter.DexSplitter { public static void main(java.lang.String[]); }
+
-keep public class com.android.tools.r8.Version { public static java.lang.String getVersionString(); }
+-keep public class com.android.tools.r8.Version { public static int getMajorVersion(); }
+-keep public class com.android.tools.r8.Version { public static int getMinorVersion(); }
+-keep public class com.android.tools.r8.Version { public static int getPatchVersion(); }
+-keep public class com.android.tools.r8.Version { public static java.lang.String getPreReleaseString(); }
+-keep public class com.android.tools.r8.Version { public static boolean isDevelopmentVersion(); }
-keepattributes LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
index 4c14226..7203f39 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.Version;
import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
@@ -59,6 +60,8 @@
* </pre>
*/
public static void main(String[] args) {
+ // Check version API
+ checkVersionApi();
// Parse arguments with the commandline parser to make use of its API.
R8Command.Builder cmd = R8Command.parse(args, origin);
CompilationMode mode = cmd.getMode();
@@ -507,4 +510,25 @@
}
}
}
+
+ private static void checkVersionApi() {
+ if (Version.getVersionString() == null) {
+ throw new RuntimeException("Expected getVersionString API");
+ }
+ if (Version.getMajorVersion() < -1) {
+ throw new RuntimeException("Expected getMajorVersion API");
+ }
+ if (Version.getMinorVersion() < -1) {
+ throw new RuntimeException("Expected getMinorVersion API");
+ }
+ if (Version.getPatchVersion() < -1) {
+ throw new RuntimeException("Expected getPatchVersion API");
+ }
+ if (Version.getPreReleaseString() == null && false) {
+ throw new RuntimeException("Expected getPreReleaseString API");
+ }
+ if (Version.isDevelopmentVersion() && false) {
+ throw new RuntimeException("Expected isDevelopmentVersion API");
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index db0c7b2..a483cfa 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -191,6 +191,10 @@
return self();
}
+ public T allowClassInlinerGracefulExit() {
+ return addOptionsModification(options -> options.testing.allowClassInlinerGracefulExit = true);
+ }
+
public T allowUnusedProguardConfigurationRules() {
return addOptionsModification(
options -> options.testing.allowUnusedProguardConfigurationRules = true);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index b90b40c..023e0e7 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -19,13 +19,33 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
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.DexProto;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.origin.Origin;
+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.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerFactory;
+import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.ProguardClassNameList;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardKeepRule;
+import com.android.tools.r8.shaking.ProguardKeepRule.Builder;
+import com.android.tools.r8.shaking.ProguardKeepRuleType;
+import com.android.tools.r8.shaking.ProguardTypeMatcher;
+import com.android.tools.r8.shaking.RootSetBuilder;
+import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.serviceloader.ServiceLoaderMultipleTest.Greeter;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -34,6 +54,7 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.PreloadedClassFileProvider;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -52,6 +73,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -66,6 +88,8 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -387,6 +411,11 @@
return buildClasses(programClasses, libraryClasses).build();
}
+ protected static AndroidApp.Builder buildClasses(Collection<Class<?>> programClasses)
+ throws IOException {
+ return buildClasses(programClasses, Collections.emptyList());
+ }
+
protected static AndroidApp.Builder buildClasses(
Collection<Class<?>> programClasses, Collection<Class<?>> libraryClasses) throws IOException {
AndroidApp.Builder builder = AndroidApp.builder();
@@ -527,6 +556,73 @@
}
}
+ protected static AppView<AppInfoWithSubtyping> computeAppViewWithSubtyping(AndroidApp app)
+ throws Exception {
+ Timing timing = new Timing();
+ InternalOptions options = new InternalOptions();
+ DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
+ AppView<AppInfoWithSubtyping> appView =
+ AppView.createForR8(new AppInfoWithSubtyping(application), options);
+ appView.setAppServices(AppServices.builder(appView).build());
+ return appView;
+ }
+
+ protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
+ AndroidApp app, Class<?> mainClass) throws Exception {
+ AppView<AppInfoWithSubtyping> appView = computeAppViewWithSubtyping(app);
+ // Run the tree shaker to compute an instance of AppInfoWithLiveness.
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ DexApplication application = appView.appInfo().app();
+ RootSet rootSet =
+ new RootSetBuilder(
+ appView, application, buildKeepRuleForClass(mainClass, application.dexItemFactory))
+ .run(executor);
+ AppInfoWithLiveness appInfoWithLiveness =
+ EnqueuerFactory.createForInitialTreeShaking(appView)
+ .traceApplication(rootSet, ProguardClassFilter.empty(), executor, application.timing);
+ // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
+ // due to liveness.
+ return appView.setAppInfo(appInfoWithLiveness);
+ }
+
+ protected static DexType buildType(Class<?> clazz, DexItemFactory factory) {
+ return buildType(Reference.classFromClass(clazz), factory);
+ }
+
+ protected static DexType buildType(TypeReference type, DexItemFactory factory) {
+ return factory.createType(type.getDescriptor());
+ }
+
+ protected static DexMethod buildMethod(Method method, DexItemFactory factory) {
+ return buildMethod(Reference.methodFromMethod(method), factory);
+ }
+
+ protected static DexMethod buildMethod(MethodReference method, DexItemFactory factory) {
+ return factory.createMethod(
+ buildType(method.getHolderClass(), factory),
+ buildProto(method.getReturnType(), method.getFormalTypes(), factory),
+ method.getMethodName());
+ }
+
+ protected static DexProto buildProto(
+ TypeReference returnType, List<TypeReference> formalTypes, DexItemFactory factory) {
+ return factory.createProto(
+ returnType == null ? factory.voidType : buildType(returnType, factory),
+ ListUtils.map(formalTypes, type -> buildType(type, factory)));
+ }
+
+ private static List<ProguardConfigurationRule> buildKeepRuleForClass(
+ Class clazz, DexItemFactory factory) {
+ Builder keepRuleBuilder = ProguardKeepRule.builder();
+ keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
+ keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+ keepRuleBuilder.setClassNames(
+ ProguardClassNameList.singletonList(
+ ProguardTypeMatcher.create(
+ factory.createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())))));
+ return Collections.singletonList(keepRuleBuilder.build());
+ }
+
/** Returns a list containing all the data resources in the given app. */
public static List<DataEntryResource> getDataResources(AndroidApp app) throws ResourceException {
List<DataEntryResource> dataResources = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 882459b..5d8e69b 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -33,6 +33,7 @@
public static final Consumer<InternalOptions> DEFAULT_OPTIONS =
options -> {
+ options.testing.allowClassInlinerGracefulExit = false;
options.testing.allowUnusedProguardConfigurationRules = false;
options.testing.reportUnusedProguardConfigurationRules = true;
};
diff --git a/src/test/java/com/android/tools/r8/VersionTests.java b/src/test/java/com/android/tools/r8/VersionTests.java
new file mode 100644
index 0000000..ba5ee78
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/VersionTests.java
@@ -0,0 +1,65 @@
+// 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;
+
+import static com.android.tools.r8.Version.LABEL;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+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 VersionTests extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private final TestParameters parameters;
+
+ public VersionTests(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testSemVerInfo() {
+ int majorVersion = Version.getMajorVersion();
+ int minorVersion = Version.getMinorVersion();
+ int patchVersion = Version.getPatchVersion();
+ String preReleaseString = Version.getPreReleaseString();
+ if (LABEL.equals("master")) {
+ assertEquals(-1, majorVersion);
+ assertEquals(-1, minorVersion);
+ assertEquals(-1, patchVersion);
+ assertNull(preReleaseString);
+ assertTrue(Version.getVersionString().startsWith("master"));
+ } else {
+ assertTrue(majorVersion > 0);
+ assertTrue(minorVersion >= 0);
+ assertTrue(patchVersion >= 0);
+ assertNotNull(preReleaseString);
+ assertTrue(
+ Version.getVersionString()
+ .startsWith(
+ ""
+ + majorVersion
+ + "."
+ + minorVersion
+ + "."
+ + patchVersion
+ + (preReleaseString.isEmpty() ? "" : "-" + preReleaseString)));
+ }
+ }
+
+ @Test
+ public void testDevelopmentPredicate() {
+ assertEquals(LABEL.equals("master") || LABEL.contains("-dev"), Version.isDevelopmentVersion());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java b/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
new file mode 100644
index 0000000..786177c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
@@ -0,0 +1,153 @@
+// 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.graph.access;
+
+import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+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.ResolutionResult;
+import com.android.tools.r8.ir.optimize.NestUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NestMethodAccessTest extends TestBase {
+ static final String EXPECTED = StringUtils.lines("A::bar", "A::baz");
+
+ private final TestParameters parameters;
+ private final boolean inSameNest;
+
+ @Parameterized.Parameters(name = "{0}, in-same-nest:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters()
+ .withCfRuntimesStartingFromIncluding(JDK11)
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .build(),
+ BooleanUtils.values());
+ }
+
+ public NestMethodAccessTest(TestParameters parameters, boolean inSameNest) {
+ this.parameters = parameters;
+ this.inSameNest = inSameNest;
+ }
+
+ public Collection<Class<?>> getClasses() {
+ return ImmutableList.of(Main.class);
+ }
+
+ public Collection<byte[]> getTransformedClasses() throws Exception {
+ return ImmutableList.of(
+ withNest(A.class)
+ .setPrivate(A.class.getDeclaredMethod("bar"))
+ .setPrivate(A.class.getDeclaredMethod("baz"))
+ .transform(),
+ withNest(B.class).transform());
+ }
+
+ @Test
+ public void testResolutionAccess() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexProgramClass bClass =
+ appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
+ DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+ // TODO(b/145187573): Update to check the full access control once possible.
+ assertEquals(
+ inSameNest,
+ NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkExpectedResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .disassemble()
+ .apply(
+ result -> {
+ if (parameters.isDexRuntime() && inSameNest) {
+ // TODO(b/145187969): R8 incorrectly compiles the nest based access away.
+ result.assertFailureWithErrorThatMatches(
+ containsString(NullPointerException.class.getName()));
+ } else {
+ checkExpectedResult(result);
+ }
+ });
+ }
+
+ private void checkExpectedResult(TestRunResult<?> result) {
+ if (inSameNest) {
+ result.assertSuccessWithOutput(EXPECTED);
+ } else {
+ result.assertFailureWithErrorThatMatches(containsString(IllegalAccessError.class.getName()));
+ }
+ }
+
+ private ClassFileTransformer withNest(Class<?> clazz) throws Exception {
+ if (inSameNest) {
+ // If in the same nest make A host and B a member.
+ return transformer(clazz).setNest(A.class, B.class);
+ }
+ // Otherwise, set the class to be its own host and no additional members.
+ return transformer(clazz).setNest(clazz);
+ }
+
+ static class A {
+ /* will be private */ void bar() {
+ System.out.println("A::bar");
+ }
+
+ /* will be private */ static void baz() {
+ System.out.println("A::baz");
+ }
+ }
+
+ static class B {
+ public void foo() {
+ // Virtual invoke to private method.
+ new A().bar();
+ // Static invoke to private method.
+ A.baz();
+ }
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new B().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
index 816288d..283f479 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.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.graph;
+package com.android.tools.r8.graph.invokespecial;
import static org.junit.Assert.assertEquals;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
index a54fa1d..1477df4 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.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.graph;
+package com.android.tools.r8.graph.invokespecial;
import static org.junit.Assert.assertEquals;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
index f49c764..24ae2fc 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.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.graph;
+package com.android.tools.r8.graph.invokespecial;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
index 1253df8..647881d 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.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.graph;
+package com.android.tools.r8.graph.invokespecial;
import static org.junit.Assert.assertEquals;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
index e046730..5343c5a 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.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.graph;
+package com.android.tools.r8.graph.invokespecial;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
index 00f4bb3..7fae808 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.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.graph;
+package com.android.tools.r8.graph.invokespecial;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
index 83d0b83..44b676a 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// 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.graph;
+package com.android.tools.r8.graph.invokespecial;
import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
import static org.hamcrest.CoreMatchers.containsString;
@@ -13,9 +13,6 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.graph.invokespecial.Main;
-import com.android.tools.r8.graph.invokespecial.TestClass;
-import com.android.tools.r8.graph.invokespecial.TestClassDump;
import com.android.tools.r8.utils.StringUtils;
import java.nio.file.Path;
import org.junit.BeforeClass;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
new file mode 100644
index 0000000..2a2ad92
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
@@ -0,0 +1,80 @@
+// 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.callsites;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class HashCodeTest extends TestBase {
+ private static final Class<?> MAIN = TestClass.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public HashCodeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(HashCodeTest.class)
+ .addKeepMainRule(MAIN)
+ .enableMergeAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("10");
+ }
+
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ // TODO(b/139246447): should avoid visiting A#<init>, which is trivial, default init!
+ assert encodedMethod.method.holder.toSourceString().endsWith("A")
+ && encodedMethod.toSourceString().contains("<init>")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ A obj = System.currentTimeMillis() > 0 ? new B() : new C();
+ System.out.println(obj.hashCode());
+ }
+ }
+
+ @NeverMerge
+ static class A {
+ @Override
+ public int hashCode() {
+ return 1;
+ }
+ }
+
+ @NeverMerge
+ static class B extends A {
+ @Override
+ public int hashCode() {
+ return super.hashCode() * 7 + 3;
+ }
+ }
+
+ static class C extends B {
+ @Override
+ public int hashCode() {
+ return super.hashCode() * 31 + 17;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
index 8e9e61f..b804cbe 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
@@ -1,7 +1,7 @@
// 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
index 354ba3b..c69f0c1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
@@ -1,7 +1,7 @@
// 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
index 3822a49..3e87f06 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
@@ -1,7 +1,7 @@
// 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
index ba8c1fd..19172e7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
@@ -1,7 +1,7 @@
// 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
index e600ebc..4fe7fde 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -1,7 +1,7 @@
// 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index baea5e4..5f0a56c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -65,6 +65,7 @@
if (methodName.equals("test")) {
upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
} else {
+ // TODO(b/139246447): should avoid visiting <init>, which is trivial, default init!
// For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
// the default initializer is invoked, the receiver had a refined type, `Sub1`.
upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index e3bd783..f31919a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -60,6 +60,7 @@
: "Unexpected revisit: " + encodedMethod.toSourceString();
CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
// `arg` for `test` or the receiver of `Base#<init>`.
+ // TODO(b/139246447): should avoid visiting <init>, which is trivial, default init!
// For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
// the default initializer is invoked, the receiver had a refined type, `Sub1`.
TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index b6dae68..81da3d5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -65,6 +65,7 @@
if (methodName.equals("m")) {
upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
} else {
+ // TODO(b/139246447): should avoid visiting <init>, which is trivial, default init!
// For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
// the default initializer is invoked, the receiver had a refined type, `Sub1`.
upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
index 37e5a31..a1042ce 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
@@ -5,12 +5,14 @@
package com.android.tools.r8.ir.optimize.classinliner;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -21,29 +23,31 @@
@RunWith(Parameterized.class)
public class BuilderWithInheritanceTest extends TestBase {
- private final Backend backend;
+ private final TestParameters parameters;
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public BuilderWithInheritanceTest(Backend backend) {
- this.backend = backend;
+ public BuilderWithInheritanceTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
public void test() throws Exception {
CodeInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addInnerClasses(BuilderWithInheritanceTest.class)
.addKeepMainRule(TestClass.class)
+ // TODO(b/145090972): Should never need to exit gracefully during testing.
+ .allowClassInlinerGracefulExit()
.enableInliningAnnotations()
.enableMergeAnnotations()
- .run(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput("42")
.inspector();
- assertThat(inspector.clazz(Builder.class), isPresent());
+ assertThat(inspector.clazz(Builder.class), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java
new file mode 100644
index 0000000..2062667
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java
@@ -0,0 +1,84 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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.CodeInspector;
+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 EscapeFromParentConstructorTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EscapeFromParentConstructorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EscapeFromParentConstructorTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // The receiver escapes from BuilderBase.<init>(), and therefore, Builder is not considered
+ // eligible for class inlining.
+ assertThat(inspector.clazz(BuilderBase.class), isPresent());
+ assertThat(inspector.clazz(Builder.class), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new Builder().build());
+ }
+ }
+
+ @NeverMerge
+ static class BuilderBase {
+
+ String greeting;
+
+ BuilderBase() {
+ initialize();
+ }
+
+ @NeverInline
+ void initialize() {
+ this.greeting = "Hello world!";
+ }
+ }
+
+ static class Builder extends BuilderBase {
+
+ String build() {
+ return greeting;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java
new file mode 100644
index 0000000..c95fa85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java
@@ -0,0 +1,216 @@
+// 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.classinliner.cost;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.codeinspector.CodeInspector;
+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 NonMaterializingFieldAccessesAfterClassInliningTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public NonMaterializingFieldAccessesAfterClassInliningTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(NonMaterializingFieldAccessesAfterClassInliningTest.class)
+ .addKeepMainRule(TestClass.class)
+ // Should be able to class inline Builder even when the threshold is low.
+ .addOptionsModification(options -> options.classInliningInstructionAllowance = 3)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Greeter.class), isPresent());
+ assertThat(inspector.clazz(Builder.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(
+ new Builder()
+ .setC1('H')
+ .setC2('e')
+ .setC3('l')
+ .setC4('l')
+ .setC5('o')
+ .setC6(' ')
+ .setC7('w')
+ .setC8('o')
+ .setC9('r')
+ .setC10('l')
+ .setC11('d')
+ .setC12('!')
+ .build());
+ }
+ }
+
+ static class Greeter {
+
+ final char c1;
+ final char c2;
+ final char c3;
+ final char c4;
+ final char c5;
+ final char c6;
+ final char c7;
+ final char c8;
+ final char c9;
+ final char c10;
+ final char c11;
+ final char c12;
+
+ Greeter(
+ char c1,
+ char c2,
+ char c3,
+ char c4,
+ char c5,
+ char c6,
+ char c7,
+ char c8,
+ char c9,
+ char c10,
+ char c11,
+ char c12) {
+ this.c1 = c1;
+ this.c2 = c2;
+ this.c3 = c3;
+ this.c4 = c4;
+ this.c5 = c5;
+ this.c6 = c6;
+ this.c7 = c7;
+ this.c8 = c8;
+ this.c9 = c9;
+ this.c10 = c10;
+ this.c11 = c11;
+ this.c12 = c12;
+ }
+
+ @Override
+ public String toString() {
+ return Character.toString(c1) + c2 + c3 + c4 + c5 + c6 + c7 + c8 + c9 + c10 + c11 + c12;
+ }
+ }
+
+ static class Builder {
+
+ char c1;
+ char c2;
+ char c3;
+ char c4;
+ char c5;
+ char c6;
+ char c7;
+ char c8;
+ char c9;
+ char c10;
+ char c11;
+ char c12;
+
+ @NeverInline
+ Builder setC1(char c) {
+ this.c1 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC2(char c) {
+ this.c2 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC3(char c) {
+ this.c3 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC4(char c) {
+ this.c4 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC5(char c) {
+ this.c5 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC6(char c) {
+ this.c6 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC7(char c) {
+ this.c7 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC8(char c) {
+ this.c8 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC9(char c) {
+ this.c9 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC10(char c) {
+ this.c10 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC11(char c) {
+ this.c11 = c;
+ return this;
+ }
+
+ @NeverInline
+ Builder setC12(char c) {
+ this.c12 = c;
+ return this;
+ }
+
+ @NeverInline
+ Greeter build() {
+ return new Greeter(c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
new file mode 100644
index 0000000..cdb5538
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -0,0 +1,19 @@
+// 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.kotlin.metadata;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+abstract class KotlinMetadataTestBase extends KotlinTestBase {
+
+ KotlinMetadataTestBase(KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ }
+
+ static final String PKG_PREFIX =
+ DescriptorUtils.getBinaryNameFromJavaType(
+ KotlinMetadataTestBase.class.getPackage().getName());
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
index c7ba86e..3ba5ecd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
@@ -11,16 +11,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import java.nio.file.Path;
import java.util.Collection;
@@ -30,7 +27,7 @@
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class MetadataRenameTest extends KotlinTestBase {
+public class MetadataRenameInExtensionTest extends KotlinMetadataTestBase {
private final TestParameters parameters;
@@ -40,33 +37,19 @@
getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
}
- public MetadataRenameTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+ public MetadataRenameInExtensionTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
super(targetVersion);
this.parameters = parameters;
}
- private static final String PKG_PREFIX =
- DescriptorUtils.getBinaryNameFromJavaType(MetadataRenameTest.class.getPackage().getName());
- private static Path supertypeLibJar;
private static Path extLibJar;
@BeforeClass
public static void createLibJar() throws Exception {
- String supertypeLibFolder = PKG_PREFIX + "/supertype_lib";
- supertypeLibJar = getStaticTemp().newFile("supertype_lib.jar").toPath();
- ProcessResult processResult =
- ToolHelper.runKotlinc(
- null,
- supertypeLibJar,
- null,
- getKotlinFileInTest(supertypeLibFolder, "impl"),
- getKotlinFileInTest(supertypeLibFolder + "/internal", "itf")
- );
- assertEquals(0, processResult.exitCode);
-
String extLibFolder = PKG_PREFIX + "/extension_lib";
extLibJar = getStaticTemp().newFile("ext_lib.jar").toPath();
- processResult =
+ ProcessResult processResult =
ToolHelper.runKotlinc(
null,
extLibJar,
@@ -77,55 +60,7 @@
}
@Test
- public void b143687784() throws Exception {
- assumeTrue(parameters.getRuntime().isCf());
-
- R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
- .addProgramFiles(supertypeLibJar)
- // Keep non-private members except for ones in `internal` definitions.
- .addKeepRules("-keep public class !**.internal.**, * { !private *; }")
- .addKeepAttributes("*Annotation*")
- .compile();
- String pkg = getClass().getPackage().getName();
- final String itfClassName = pkg + ".supertype_lib.internal.Itf";
- final String implClassName = pkg + ".supertype_lib.Impl";
- compileResult.inspect(inspector -> {
- ClassSubject itf = inspector.clazz(itfClassName);
- assertThat(itf, not(isPresent()));
-
- ClassSubject impl = inspector.clazz(implClassName);
- assertThat(impl, isPresent());
- assertThat(impl, not(isRenamed()));
- // API entry is kept, hence the presence of Metadata.
- DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
- assertNotNull(metadata);
- assertThat(metadata.toString(), not(containsString("internal")));
- assertThat(metadata.toString(), not(containsString("Itf")));
- });
-
- Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
- compileResult.writeToZip(r8ProcessedLibZip);
-
- String appFolder = PKG_PREFIX + "/supertype_app";
- Path output =
- kotlinc(parameters.getRuntime().asCf())
- .addClasspathFiles(r8ProcessedLibZip)
- .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
- .setOutputPath(temp.newFolder().toPath())
- .compile();
-
- testForJvm()
- .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
- .addClasspath(output)
- .run(parameters.getRuntime(), pkg + ".supertype_app.MainKt")
- .assertSuccessWithOutputLines("Impl::foo", "Program::foo");
- }
-
- @Test
- public void testMetadataInExtension() throws Exception {
- assumeTrue(parameters.getRuntime().isCf());
-
+ public void testMetadataInExtension_merged() throws Exception {
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
.addProgramFiles(extLibJar)
@@ -161,11 +96,61 @@
kotlinc(parameters.getRuntime().asCf())
.addClasspathFiles(r8ProcessedLibZip)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
- // TODO(b/143687784): update to just .compile() once fixed.
.setOutputPath(temp.newFolder().toPath())
+ // TODO(b/143687784): update to just .compile() once fixed.
.compileRaw();
// TODO(b/143687784): should be able to compile!
assertNotEquals(0, kotlinTestCompileResult.exitCode);
assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
}
+
+ @Test
+ public void testMetadataInExtension_renamed() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(extLibJar)
+ // Keep the B class and its interface (which has the doStuff method).
+ .addKeepRules("-keep class **.B")
+ .addKeepRules("-keep class **.I { <methods>; }")
+ // Keep Super, but allow minification.
+ .addKeepRules("-keep,allowobfuscation class **.Super")
+ // Keep the BKt extension method which requires metadata
+ // to be called with Kotlin syntax from other kotlin code.
+ .addKeepRules("-keep class **.BKt { <methods>; }")
+ .addKeepAttributes("*Annotation*")
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String superClassName = pkg + ".extension_lib.Super";
+ final String bClassName = pkg + ".extension_lib.B";
+ compileResult.inspect(inspector -> {
+ ClassSubject sup = inspector.clazz(superClassName);
+ assertThat(sup, isPresent());
+ assertThat(sup, isRenamed());
+
+ ClassSubject impl = inspector.clazz(bClassName);
+ assertThat(impl, isPresent());
+ assertThat(impl, not(isRenamed()));
+ // API entry is kept, hence the presence of Metadata.
+ DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
+ assertNotNull(metadata);
+ assertThat(metadata.toString(), not(containsString("Super")));
+ });
+
+ Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
+ compileResult.writeToZip(r8ProcessedLibZip);
+
+ String appFolder = PKG_PREFIX + "/extension_app";
+ Path output =
+ kotlinc(parameters.getRuntime().asCf())
+ .addClasspathFiles(r8ProcessedLibZip)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".extension_app.MainKt")
+ .assertSuccessWithOutputLines("do stuff", "do stuff");
+ }
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
similarity index 68%
copy from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
copy to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
index c7ba86e..7001909 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
@@ -9,18 +9,14 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import java.nio.file.Path;
import java.util.Collection;
@@ -30,7 +26,7 @@
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class MetadataRenameTest extends KotlinTestBase {
+public class MetadataRenameInSupertypeTest extends KotlinMetadataTestBase {
private final TestParameters parameters;
@@ -40,15 +36,13 @@
getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
}
- public MetadataRenameTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+ public MetadataRenameInSupertypeTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
super(targetVersion);
this.parameters = parameters;
}
- private static final String PKG_PREFIX =
- DescriptorUtils.getBinaryNameFromJavaType(MetadataRenameTest.class.getPackage().getName());
private static Path supertypeLibJar;
- private static Path extLibJar;
@BeforeClass
public static void createLibJar() throws Exception {
@@ -63,23 +57,10 @@
getKotlinFileInTest(supertypeLibFolder + "/internal", "itf")
);
assertEquals(0, processResult.exitCode);
-
- String extLibFolder = PKG_PREFIX + "/extension_lib";
- extLibJar = getStaticTemp().newFile("ext_lib.jar").toPath();
- processResult =
- ToolHelper.runKotlinc(
- null,
- extLibJar,
- null,
- getKotlinFileInTest(extLibFolder, "B")
- );
- assertEquals(0, processResult.exitCode);
}
@Test
- public void b143687784() throws Exception {
- assumeTrue(parameters.getRuntime().isCf());
-
+ public void b143687784_merged() throws Exception {
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
.addProgramFiles(supertypeLibJar)
@@ -123,49 +104,50 @@
}
@Test
- public void testMetadataInExtension() throws Exception {
- assumeTrue(parameters.getRuntime().isCf());
-
+ public void b143687784_renamed() throws Exception {
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
- .addProgramFiles(extLibJar)
- // Keep the B class and its interface (which has the doStuff method).
- .addKeepRules("-keep class **.B")
- .addKeepRules("-keep class **.I { <methods>; }")
- // Keep the BKt extension method which requires metadata
- // to be called with Kotlin syntax from other kotlin code.
- .addKeepRules("-keep class **.BKt { <methods>; }")
+ .addProgramFiles(supertypeLibJar)
+ // Keep non-private members except for ones in `internal` definitions.
+ .addKeepRules("-keep public class !**.internal.**, * { !private *; }")
+ // Keep `internal` definitions, but allow minification.
+ .addKeepRules("-keep,allowobfuscation class **.internal.** { *; }")
.addKeepAttributes("*Annotation*")
.compile();
String pkg = getClass().getPackage().getName();
- final String superClassName = pkg + ".extension_lib.Super";
- final String bClassName = pkg + ".extension_lib.B";
+ final String itfClassName = pkg + ".supertype_lib.internal.Itf";
+ final String implClassName = pkg + ".supertype_lib.Impl";
compileResult.inspect(inspector -> {
- ClassSubject sup = inspector.clazz(superClassName);
- assertThat(sup, not(isPresent()));
+ ClassSubject itf = inspector.clazz(itfClassName);
+ assertThat(itf, isPresent());
+ assertThat(itf, isRenamed());
- ClassSubject impl = inspector.clazz(bClassName);
+ ClassSubject impl = inspector.clazz(implClassName);
assertThat(impl, isPresent());
assertThat(impl, not(isRenamed()));
// API entry is kept, hence the presence of Metadata.
DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
assertNotNull(metadata);
- assertThat(metadata.toString(), not(containsString("Super")));
+ assertThat(metadata.toString(), not(containsString("internal")));
+ assertThat(metadata.toString(), not(containsString("Itf")));
+ assertThat(metadata.toString(), containsString("a/a"));
});
Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
compileResult.writeToZip(r8ProcessedLibZip);
- String appFolder = PKG_PREFIX + "/extension_app";
- ProcessResult kotlinTestCompileResult =
+ String appFolder = PKG_PREFIX + "/supertype_app";
+ Path output =
kotlinc(parameters.getRuntime().asCf())
.addClasspathFiles(r8ProcessedLibZip)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
- // TODO(b/143687784): update to just .compile() once fixed.
.setOutputPath(temp.newFolder().toPath())
- .compileRaw();
- // TODO(b/143687784): should be able to compile!
- assertNotEquals(0, kotlinTestCompileResult.exitCode);
- assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".supertype_app.MainKt")
+ .assertSuccessWithOutputLines("Impl::foo", "Program::foo");
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 155c91c..fa46140 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -15,8 +15,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.Collection;
@@ -53,6 +51,8 @@
.addKeepMainRule(mainClassName)
.addKeepAttributes("*Annotation*")
.addKeepRules("-keep class kotlin.Metadata")
+ // TODO(b/145090972): Should never need to exit gracefully during testing.
+ .allowClassInlinerGracefulExit()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), mainClassName);
CodeInspector inspector = result.inspector();
@@ -67,5 +67,4 @@
// All other classes can be renamed, hence the absence of Metadata;
assertNull(retrieveMetadata(impl1.getDexClass()));
}
-
}
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index ec01f62..b1bcb52 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -6,17 +6,12 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.AppServices;
-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.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.resolution.singletarget.Main;
import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
@@ -34,26 +29,10 @@
import com.android.tools.r8.resolution.singletarget.two.OtherSubSubClassOne;
import com.android.tools.r8.resolution.singletarget.two.OtherSubSubClassTwo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.EnqueuerFactory;
-import com.android.tools.r8.shaking.ProguardClassFilter;
-import com.android.tools.r8.shaking.ProguardClassNameList;
-import com.android.tools.r8.shaking.ProguardConfigurationRule;
-import com.android.tools.r8.shaking.ProguardKeepRule;
-import com.android.tools.r8.shaking.ProguardKeepRule.Builder;
-import com.android.tools.r8.shaking.ProguardKeepRuleType;
-import com.android.tools.r8.shaking.ProguardTypeMatcher;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.BeforeClass;
@@ -103,42 +82,11 @@
this.allTargetHolders = allTargetHolders;
}
- public static AppInfoWithLiveness createAppInfoWithLiveness(AndroidApp app, Class<?> mainClass)
- throws Exception {
- // Run the tree shaker to compute an instance of AppInfoWithLiveness.
- Timing timing = new Timing();
- InternalOptions options = new InternalOptions();
- DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
- AppView<? extends AppInfoWithSubtyping> appView =
- AppView.createForR8(new AppInfoWithSubtyping(application), options);
- appView.setAppServices(AppServices.builder(appView).build());
-
- ExecutorService executor = Executors.newSingleThreadExecutor();
- RootSet rootSet =
- new RootSetBuilder(
- appView, application, buildKeepRuleForClass(mainClass, application.dexItemFactory))
- .run(executor);
- return EnqueuerFactory.createForInitialTreeShaking(appView)
- .traceApplication(rootSet, ProguardClassFilter.empty(), executor, timing);
- // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
- // due to liveness.
- }
-
@BeforeClass
public static void computeAppInfo() throws Exception {
- appInfo = createAppInfoWithLiveness(readClassesAndAsmDump(CLASSES, ASM_CLASSES), Main.class);
- }
-
- private static List<ProguardConfigurationRule> buildKeepRuleForClass(Class clazz,
- DexItemFactory factory) {
- Builder keepRuleBuilder = ProguardKeepRule.builder();
- keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
- keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
- keepRuleBuilder.setClassNames(
- ProguardClassNameList.singletonList(
- ProguardTypeMatcher.create(
- factory.createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())))));
- return Collections.singletonList(keepRuleBuilder.build());
+ appInfo =
+ computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, ASM_CLASSES), Main.class)
+ .appInfo();
}
private static Object[] singleTarget(String name, Class<?> receiverAndTarget) {
@@ -244,19 +192,14 @@
});
}
- public static DexMethod buildMethod(Class clazz, String name, AppInfo appInfo) {
- return appInfo
- .dexItemFactory()
- .createMethod(
- toType(clazz, appInfo),
- appInfo.dexItemFactory().createProto(appInfo.dexItemFactory().voidType),
- name);
+ public static DexMethod buildNullaryVoidMethod(Class clazz, String name, AppInfo appInfo) {
+ return buildMethod(
+ Reference.method(Reference.classFromClass(clazz), name, Collections.emptyList(), null),
+ appInfo.dexItemFactory());
}
- public static DexType toType(Class clazz, AppInfo appInfo) {
- return appInfo
- .dexItemFactory()
- .createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()));
+ private static DexType toType(Class clazz, AppInfo appInfo) {
+ return buildType(clazz, appInfo.dexItemFactory());
}
private final String methodName;
@@ -266,7 +209,7 @@
@Test
public void lookupSingleTarget() {
- DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
+ DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo);
Assert.assertNotNull(
appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method, method.holder);
@@ -281,7 +224,7 @@
@Test
public void lookupVirtualTargets() {
- DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
+ DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo);
Assert.assertNotNull(
appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index ccdb49b..5fe17d5 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -59,11 +59,11 @@
@BeforeClass
public static void computeAppInfo() throws Exception {
- appInfo = SingleTargetLookupTest.createAppInfoWithLiveness(readClasses(CLASSES), Main.class);
+ appInfo = computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
}
private static DexMethod buildMethod(Class clazz, String name) {
- return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+ return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
}
@Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index f3191ac..22445e6 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -108,12 +108,11 @@
@BeforeClass
public static void computeAppInfo() throws Exception {
appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- readClassesAndAsmDump(CLASSES, DUMPS), Main.class);
+ computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, DUMPS), Main.class).appInfo();
}
private static DexMethod buildMethod(Class clazz, String name) {
- return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+ return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
}
@Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index 7c435d3..63aa76f 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -95,12 +95,11 @@
@BeforeClass
public static void computeAppInfo() throws Exception {
appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- readClassesAndAsmDump(CLASSES, DUMPS), Main.class);
+ computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, DUMPS), Main.class).appInfo();
}
private static DexMethod buildMethod(Class clazz, String name) {
- return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+ return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
}
@Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index e876ce0..22a12f2 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -142,12 +142,11 @@
@BeforeClass
public static void computeAppInfo() throws Exception {
appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- readClassesAndAsmDump(CLASSES, DUMPS), Main.class);
+ computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, DUMPS), Main.class).appInfo();
}
private static DexMethod buildMethod(Class clazz, String name) {
- return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+ return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
}
@Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
index c9a1c5e..2117ed6 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
@@ -15,8 +15,8 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
-import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,10 +43,9 @@
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
- AppInfoWithLiveness appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
- DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ AndroidApp app = readClasses(CLASSES);
+ AppInfoWithLiveness appInfo = computeAppViewWithLiveness(app, Main.class).appInfo();
+ DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
// Currently R8 will resolve to L::f as that is the first in the topological search.
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index 075e399..a9c79fc 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -44,12 +44,13 @@
// The resolution is runtime independent, so just run it on the default CF VM.
assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
AppInfoWithLiveness appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(transformB()))
- .build(),
- Main.class);
- DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ computeAppViewWithLiveness(
+ buildClasses(CLASSES)
+ .addClassProgramData(Collections.singletonList(transformB()))
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index 58d0891..a65c188 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -44,12 +44,13 @@
// The resolution is runtime independent, so just run it on the default CF VM.
assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
AppInfoWithLiveness appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(transformB()))
- .build(),
- Main.class);
- DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ computeAppViewWithLiveness(
+ buildClasses(CLASSES)
+ .addClassProgramData(Collections.singletonList(transformB()))
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index 30295fd..053fba0 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -47,12 +47,13 @@
// The resolution is runtime independent, so just run it on the default CF VM.
assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
AppInfoWithLiveness appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(transformB()))
- .build(),
- Main.class);
- DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ computeAppViewWithLiveness(
+ buildClasses(CLASSES)
+ .addClassProgramData(Collections.singletonList(transformB()))
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 6c4070e..5163bc2 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -47,12 +47,13 @@
// The resolution is runtime independent, so just run it on the default CF VM.
assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
AppInfoWithLiveness appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(transformB()))
- .build(),
- Main.class);
- DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ computeAppViewWithLiveness(
+ buildClasses(CLASSES)
+ .addClassProgramData(Collections.singletonList(transformB()))
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index 581554b..75f689f 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -46,12 +46,13 @@
// The resolution is runtime independent, so just run it on the default CF VM.
assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
AppInfoWithLiveness appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(transformB()))
- .build(),
- Main.class);
- DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ computeAppViewWithLiveness(
+ buildClasses(CLASSES)
+ .addClassProgramData(Collections.singletonList(transformB()))
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
Set<String> holders = new HashSet<>();
resolutionResult
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index 7ca9525..5772bdc 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
-import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,9 +43,8 @@
// The resolution is runtime independent, so just run it on the default CF VM.
assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
AppInfoWithLiveness appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
- DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
+ DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 1cbd668..50e0f8d 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
-import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,9 +43,8 @@
// The resolution is runtime independent, so just run it on the default CF VM.
assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
AppInfoWithLiveness appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
- DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
+ DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index 47d7d65..bb81c51 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -47,12 +47,13 @@
// The resolution is runtime independent, so just run it on the default CF VM.
assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
AppInfoWithLiveness appInfo =
- SingleTargetLookupTest.createAppInfoWithLiveness(
- buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(transformB()))
- .build(),
- Main.class);
- DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ computeAppViewWithLiveness(
+ buildClasses(CLASSES)
+ .addClassProgramData(Collections.singletonList(transformB()))
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
Set<String> holders = new HashSet<>();
resolutionResult
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 428f9bb..d0d40bb 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -6,6 +6,8 @@
import static org.objectweb.asm.Opcodes.ASM7;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
@@ -13,6 +15,7 @@
import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
import com.android.tools.r8.utils.DescriptorUtils;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -24,6 +27,8 @@
public class ClassFileTransformer {
+ private static final int NEST_SUPPORTED_VERSION = 55;
+
/**
* Basic algorithm for transforming the content of a class file.
*
@@ -161,6 +166,100 @@
});
}
+ public ClassFileTransformer setMinVersion(int minVersion) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(
+ Integer.max(version, minVersion), access, name, signature, superName, interfaces);
+ }
+ });
+ }
+
+ public ClassFileTransformer setNest(Class<?> host, Class<?>... members) {
+ assert !Arrays.asList(members).contains(host);
+ return setMinVersion(NEST_SUPPORTED_VERSION)
+ .addClassTransformer(
+ new ClassTransformer() {
+
+ final String hostName = DescriptorUtils.getBinaryNameFromJavaType(host.getTypeName());
+
+ final List<String> memberNames =
+ Arrays.stream(members)
+ .map(m -> DescriptorUtils.getBinaryNameFromJavaType(m.getTypeName()))
+ .collect(Collectors.toList());
+
+ String className;
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ className = name;
+ }
+
+ @Override
+ public void visitNestHost(String nestHost) {
+ // Ignore/remove existing nest information.
+ }
+
+ @Override
+ public void visitNestMember(String nestMember) {
+ // Ignore/remove existing nest information.
+ }
+
+ @Override
+ public void visitEnd() {
+ if (className.equals(hostName)) {
+ for (String memberName : memberNames) {
+ super.visitNestMember(memberName);
+ }
+ } else {
+ assert memberNames.contains(className);
+ super.visitNestHost(hostName);
+ }
+ super.visitEnd();
+ }
+ });
+ }
+
+ public ClassFileTransformer setPrivate(Method method) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ final MethodReference methodReference = Reference.methodFromMethod(method);
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ boolean isConstructor =
+ name.equals(Constants.INSTANCE_INITIALIZER_NAME)
+ || name.equals(Constants.CLASS_INITIALIZER_NAME);
+ MethodAccessFlags accessFlags =
+ MethodAccessFlags.fromCfAccessFlags(access, isConstructor);
+ if (name.equals(methodReference.getMethodName())
+ && descriptor.equals(methodReference.getMethodDescriptor())) {
+ accessFlags.unsetPublic();
+ accessFlags.unsetProtected();
+ accessFlags.setPrivate();
+ }
+ return super.visitMethod(
+ accessFlags.getAsCfAccessFlags(), name, descriptor, signature, exceptions);
+ }
+ });
+ }
+
/** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
@FunctionalInterface
public interface MethodInsnTransform {
diff --git a/tests/d8_api_usage_sample.jar b/tests/d8_api_usage_sample.jar
index 168d828..30b00cb 100644
--- a/tests/d8_api_usage_sample.jar
+++ b/tests/d8_api_usage_sample.jar
Binary files differ
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
index 168d828..4c4801c 100644
--- a/tests/r8_api_usage_sample.jar
+++ b/tests/r8_api_usage_sample.jar
Binary files differ
diff --git a/tools/r8_release.py b/tools/r8_release.py
index a848beb..e661dc6 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -68,6 +68,11 @@
'is the same as exiting release (%s).' % old_version
sys.exit(1)
+ if args.dev_pre_cherry_pick:
+ for pre_commit in args.dev_pre_cherry_pick:
+ subprocess.check_call([
+ 'git', 'cherry-pick', '--no-edit', pre_commit])
+
# Merge the desired commit from master on to the branch.
subprocess.check_call([
'git', 'merge', '--no-ff', '--no-edit', commithash])
@@ -478,6 +483,12 @@
group.add_argument('--dev-release',
metavar=('<master hash>'),
help='The hash to use for the new dev version of R8')
+ result.add_argument('--dev-pre-cherry-pick',
+ metavar=('<master hash(s)>'),
+ default=[],
+ action='append',
+ help='List of commits to cherry pick before doing full '
+ 'merge, mostly used for reverting cherry picks')
group.add_argument('--version',
metavar=('<version>'),
help='The new version of R8 (e.g., 1.4.51) to release to selected channels')
@@ -563,4 +574,3 @@
if __name__ == '__main__':
sys.exit(main())
-
diff --git a/tools/utils.py b/tools/utils.py
index 495f455..2ff2ec2 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -560,8 +560,9 @@
cmd = [jdk.GetJavaExecutable(), '-cp', path, 'com.android.tools.r8.R8',
'--version']
output = subprocess.check_output(cmd, stderr = subprocess.STDOUT)
- # output is on form 'R8 <version>' so we just strip of 'R8 '.
- return output.splitlines()[0][3:]
+ # output is of the form 'R8 <version> (with additional info)'
+ # so we split on '('; clean up tailing spaces; and strip off 'R8 '.
+ return output.split('(')[0].strip()[3:]
def desugar_configuration_version():
with open(DESUGAR_CONFIGURATION, 'r') as f: