Merge "Warn users of unidentified reflection while minification is allowed."
diff --git a/build.gradle b/build.gradle
index abefa15..b733b88 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,24 +7,6 @@
import tasks.GetJarsFromConfiguration
import utils.Utils
-apply plugin: 'java'
-apply plugin: 'idea'
-apply plugin: 'com.google.protobuf'
-apply plugin: 'com.cookpad.android.licensetools'
-apply plugin: 'net.ltgt.errorprone-base'
-apply plugin: "net.ltgt.apt"
-
-// Ensure importing into IntelliJ IDEA use the same output directories as Gradle. In tests we
-// use the output path for tests (ultimately through ToolHelper.getClassPathForTests()) and
-// therefore these paths need to be the same. See https://youtrack.jetbrains.com/issue/IDEA-175172
-// for context.
-idea {
- module {
- outputDir file('build/classes/main')
- testOutputDir file('build/classes/test')
- }
-}
-
ext {
androidSupportVersion = '25.4.0'
apacheCommonsVersion = '1.12'
@@ -68,11 +50,6 @@
apply from: 'copyAdditionalJctfCommonFiles.gradle'
-
-if (project.hasProperty('with_code_coverage')) {
- apply plugin: 'jacoco'
-}
-
repositories {
maven { url 'https://maven.google.com' }
maven { url 'https://kotlin.bintray.com/kotlinx' }
@@ -93,9 +70,39 @@
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.3'
classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
classpath "net.ltgt.gradle:gradle-apt-plugin:0.12"
+ classpath "com.gradle:build-scan-plugin:1.14"
}
}
+apply plugin: "com.gradle.build-scan"
+
+buildScan {
+ licenseAgreementUrl = 'https://gradle.com/terms-of-service'
+ licenseAgree = 'yes'
+}
+
+apply plugin: 'java'
+apply plugin: 'idea'
+apply plugin: 'com.google.protobuf'
+apply plugin: 'com.cookpad.android.licensetools'
+apply plugin: 'net.ltgt.errorprone-base'
+apply plugin: "net.ltgt.apt"
+
+// Ensure importing into IntelliJ IDEA use the same output directories as Gradle. In tests we
+// use the output path for tests (ultimately through ToolHelper.getClassPathForTests()) and
+// therefore these paths need to be the same. See https://youtrack.jetbrains.com/issue/IDEA-175172
+// for context.
+idea {
+ module {
+ outputDir file('build/classes/main')
+ testOutputDir file('build/classes/test')
+ }
+}
+
+if (project.hasProperty('with_code_coverage')) {
+ apply plugin: 'jacoco'
+}
+
// Custom source set for example tests and generated tests.
sourceSets {
test {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3d2cc25..805bdf7 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
@@ -249,12 +250,13 @@
DexApplication application =
new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
- AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+ AppView<AppInfoWithSubtyping> appView =
+ new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
RootSet rootSet;
String proguardSeedsData = null;
timing.begin("Strip unused code");
try {
- Set<DexType> missingClasses = appInfo.getMissingClasses();
+ Set<DexType> missingClasses = appView.getAppInfo().getMissingClasses();
missingClasses = filterMissingClasses(
missingClasses, options.proguardConfiguration.getDontWarnPatterns());
if (!missingClasses.isEmpty()) {
@@ -271,34 +273,50 @@
// Compute kotlin info before setting the roots and before
// kotlin metadata annotation is removed.
- computeKotlinInfoForProgramClasses(application, appInfo);
+ computeKotlinInfoForProgramClasses(application, appView.getAppInfo());
final ProguardConfiguration.Builder compatibility =
ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
- rootSet = new RootSetBuilder(
- appInfo, application, options.proguardConfiguration.getRules(), options)
+ rootSet =
+ new RootSetBuilder(
+ appView.getAppInfo(),
+ application,
+ options.proguardConfiguration.getRules(),
+ options)
.run(executorService);
ProtoLiteExtension protoLiteExtension =
- options.forceProguardCompatibility ? null : new ProtoLiteExtension(appInfo);
- Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility,
- compatibility, protoLiteExtension);
- appInfo = enqueuer.traceApplication(rootSet, executorService, timing);
+ options.forceProguardCompatibility
+ ? null
+ : new ProtoLiteExtension(appView.getAppInfo());
+ Enqueuer enqueuer =
+ new Enqueuer(
+ appView.getAppInfo(),
+ options,
+ options.forceProguardCompatibility,
+ compatibility,
+ protoLiteExtension);
+ appView.setAppInfo(enqueuer.traceApplication(rootSet, executorService, timing));
if (options.proguardConfiguration.isPrintSeeds()) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes);
- RootSetBuilder.writeSeeds(appInfo.withLiveness(), out, type -> true);
+ RootSetBuilder.writeSeeds(appView.getAppInfo().withLiveness(), out, type -> true);
out.flush();
proguardSeedsData = bytes.toString();
}
if (options.enableTreeShaking) {
- TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ TreePruner pruner =
+ new TreePruner(application, appView.getAppInfo().withLiveness(), options);
application = pruner.run();
// Recompute the subtyping information.
- appInfo = appInfo.withLiveness().prunedCopyFrom(application, pruner.getRemovedClasses());
- new AbstractMethodRemover(appInfo).run();
+ appView.setAppInfo(
+ appView
+ .getAppInfo()
+ .withLiveness()
+ .prunedCopyFrom(application, pruner.getRemovedClasses()));
+ new AbstractMethodRemover(appView.getAppInfo()).run();
}
- new AnnotationRemover(appInfo.withLiveness(), compatibility, options).run();
+ new AnnotationRemover(appView.getAppInfo().withLiveness(), compatibility, options).run();
// TODO(69445518): This is still work in progress, and this file writing is currently used
// for testing.
@@ -320,44 +338,52 @@
timing.end();
}
- GraphLense graphLense = GraphLense.getIdentityLense();
-
if (options.proguardConfiguration.isAccessModificationAllowed()) {
- graphLense = ClassAndMemberPublicizer.run(
- executorService, timing, application, appInfo, rootSet, graphLense);
+ appView.setGraphLense(
+ ClassAndMemberPublicizer.run(executorService, timing, application, appView, rootSet));
// We can now remove visibility bridges. Note that we do not need to update the
// invoke-targets here, as the existing invokes will simply dispatch to the now
// visible super-method. MemberRebinding, if run, will then dispatch it correctly.
- application = new VisibilityBridgeRemover(appInfo, application).run();
+ application = new VisibilityBridgeRemover(appView.getAppInfo(), application).run();
}
- if (appInfo.hasLiveness()) {
+ if (appView.getAppInfo().hasLiveness()) {
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
if (options.proguardConfiguration.hasApplyMappingFile()) {
SeedMapper seedMapper =
SeedMapper.seedMapperFromFile(options.proguardConfiguration.getApplyMappingFile());
timing.begin("apply-mapping");
- graphLense =
- new ProguardMapApplier(appInfo.withLiveness(), graphLense, seedMapper).run(timing);
- application = application.asDirect().rewrittenWithLense(graphLense);
- appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense);
+ appView.setGraphLense(
+ new ProguardMapApplier(appView.withLiveness(), seedMapper).run(timing));
+ application = application.asDirect().rewrittenWithLense(appView.getGraphLense());
+ appView.setAppInfo(
+ appView
+ .getAppInfo()
+ .withLiveness()
+ .rewrittenWithLense(application.asDirect(), appView.getGraphLense()));
timing.end();
}
- graphLense = new MemberRebindingAnalysis(appInfo.withLiveness(), graphLense).run();
+ appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness).run());
// Class merging requires inlining.
if (options.enableClassMerging && options.enableInlining) {
timing.begin("ClassMerger");
VerticalClassMerger classMerger =
- new VerticalClassMerger(application, appInfo.withLiveness(), graphLense, timing);
- graphLense = classMerger.run();
+ new VerticalClassMerger(application, appViewWithLiveness, timing);
+ appView.setGraphLense(classMerger.run());
timing.end();
- application = application.asDirect().rewrittenWithLense(graphLense);
- appInfo = appInfo.withLiveness()
- .prunedCopyFrom(application, classMerger.getRemovedClasses())
- .rewrittenWithLense(application.asDirect(), graphLense);
+ application = application.asDirect().rewrittenWithLense(appView.getGraphLense());
+ appViewWithLiveness.setAppInfo(
+ appViewWithLiveness
+ .getAppInfo()
+ .prunedCopyFrom(application, classMerger.getRemovedClasses())
+ .rewrittenWithLense(application.asDirect(), appView.getGraphLense()));
}
// Collect switch maps and ordinals maps.
- appInfo = new SwitchMapCollector(appInfo.withLiveness(), options).run();
- appInfo = new EnumOrdinalMapCollector(appInfo.withLiveness(), options).run();
+ appViewWithLiveness.setAppInfo(
+ new SwitchMapCollector(appViewWithLiveness.getAppInfo(), options).run());
+ appViewWithLiveness.setAppInfo(
+ new EnumOrdinalMapCollector(appViewWithLiveness.getAppInfo(), options).run());
// TODO(b/79143143): re-enable once fixed.
// graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
@@ -366,7 +392,9 @@
timing.begin("Create IR");
CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
try {
- IRConverter converter = new IRConverter(appInfo, options, timing, printer, graphLense);
+ IRConverter converter =
+ new IRConverter(
+ appView.getAppInfo(), options, timing, printer, appView.getGraphLense());
application = converter.optimize(application, executorService);
} finally {
timing.end();
@@ -386,15 +414,15 @@
// Overwrite SourceFile if specified. This step should be done after IR conversion.
timing.begin("Rename SourceFile");
- new SourceFileRewriter(appInfo, options).run();
+ new SourceFileRewriter(appView.getAppInfo(), options).run();
timing.end();
if (!options.mainDexKeepRules.isEmpty()) {
- appInfo = new AppInfoWithSubtyping(application);
- Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
+ appView.setAppInfo(new AppInfoWithSubtyping(application));
+ Enqueuer enqueuer = new Enqueuer(appView.getAppInfo(), options, true);
// Lets find classes which may have code executed before secondary dex files installation.
RootSet mainDexRootSet =
- new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options)
+ new RootSetBuilder(appView.getAppInfo(), application, options.mainDexKeepRules, options)
.run(executorService);
AppInfoWithLiveness mainDexAppInfo =
enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
@@ -409,18 +437,24 @@
.build();
}
- appInfo = new AppInfoWithSubtyping(application);
+ appView.setAppInfo(new AppInfoWithSubtyping(application));
if (options.enableTreeShaking || options.enableMinification) {
timing.begin("Post optimization code stripping");
try {
- Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
- appInfo = enqueuer.traceApplication(rootSet, executorService, timing);
+ Enqueuer enqueuer =
+ new Enqueuer(appView.getAppInfo(), options, options.forceProguardCompatibility);
+ appView.setAppInfo(enqueuer.traceApplication(rootSet, executorService, timing));
+
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
if (options.enableTreeShaking) {
- TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ TreePruner pruner =
+ new TreePruner(application, appViewWithLiveness.getAppInfo(), options);
application = pruner.run();
- appInfo = appInfo.withLiveness()
- .prunedCopyFrom(application, pruner.getRemovedClasses());
+ appViewWithLiveness.setAppInfo(
+ appViewWithLiveness
+ .getAppInfo()
+ .prunedCopyFrom(application, pruner.getRemovedClasses()));
// Print reasons on the application after pruning, so that we reflect the actual result.
ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
reasonPrinter.run(application);
@@ -440,7 +474,7 @@
// will happen. Just avoid the overhead.
NamingLens namingLens =
options.enableMinification
- ? new Minifier(appInfo.withLiveness(), rootSet, options).run(timing)
+ ? new Minifier(appView.getAppInfo().withLiveness(), rootSet, options).run(timing)
: NamingLens.getIdentityLens();
timing.end();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9fcca89..584b06d 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -68,6 +68,7 @@
Path proguardCompatibilityRulesOutput = null;
private boolean allowPartiallyImplementedProguardOptions = false;
+ private boolean allowTestProguardOptions = false;
private StringConsumer mainDexListConsumer = null;
@@ -290,7 +291,8 @@
}
ProguardConfigurationParser parser = new ProguardConfigurationParser(
- factory, reporter, !allowPartiallyImplementedProguardOptions);
+ factory, reporter,
+ !allowPartiallyImplementedProguardOptions, allowTestProguardOptions);
if (!proguardConfigs.isEmpty()) {
parser.parse(proguardConfigs);
}
@@ -393,6 +395,11 @@
void allowPartiallyImplementedProguardOptions() {
allowPartiallyImplementedProguardOptions = true;
}
+
+ // Internal for-testing method to allow proguard options only available for testing.
+ void allowTestProguardOptions() {
+ allowTestProguardOptions = true;
+ }
}
// Wrapper class to ensure that R8 does not allow DEX as program inputs.
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6549848..63c1173 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.3.4-dev";
+ public static final String LABEL = "1.3.5-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
new file mode 100644
index 0000000..64a4ce4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+
+public class AppView<T extends AppInfo> {
+
+ private T appInfo;
+ private final DexItemFactory dexItemFactory;
+ private GraphLense graphLense;
+
+ public AppView(T appInfo, GraphLense graphLense) {
+ this.appInfo = appInfo;
+ this.dexItemFactory = appInfo != null ? appInfo.dexItemFactory : null;
+ this.graphLense = graphLense;
+ }
+
+ public T getAppInfo() {
+ return appInfo;
+ }
+
+ public void setAppInfo(T appInfo) {
+ this.appInfo = appInfo;
+ }
+
+ public DexItemFactory getDexItemFactory() {
+ return dexItemFactory;
+ }
+
+ public GraphLense getGraphLense() {
+ return graphLense;
+ }
+
+ public void setGraphLense(GraphLense graphLense) {
+ this.graphLense = graphLense;
+ }
+
+ public AppView<AppInfoWithLiveness> withLiveness() {
+ return new AppViewWithLiveness();
+ }
+
+ private class AppViewWithLiveness extends AppView<AppInfoWithLiveness> {
+
+ private AppViewWithLiveness() {
+ super(null, null);
+ }
+
+ @Override
+ public AppInfoWithLiveness getAppInfo() {
+ return AppView.this.getAppInfo().withLiveness();
+ }
+
+ @Override
+ public void setAppInfo(AppInfoWithLiveness appInfoWithLiveness) {
+ @SuppressWarnings("unchecked")
+ T appInfo = (T) appInfoWithLiveness;
+ AppView.this.setAppInfo(appInfo);
+ }
+
+ @Override
+ public DexItemFactory getDexItemFactory() {
+ return AppView.this.dexItemFactory;
+ }
+
+ @Override
+ public GraphLense getGraphLense() {
+ return AppView.this.getGraphLense();
+ }
+
+ @Override
+ public void setGraphLense(GraphLense graphLense) {
+ AppView.this.setGraphLense(graphLense);
+ }
+
+ @Override
+ public AppView<AppInfoWithLiveness> withLiveness() {
+ return this;
+ }
+ }
+}
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 69126be..3586d6d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -758,6 +758,10 @@
publicized = true;
}
+ private void unsetPublicized() {
+ publicized = false;
+ }
+
private void markUseIdentifierNameString() {
useIdentifierNameString = true;
}
@@ -835,6 +839,10 @@
ensureMutableOI().markPublicized();
}
+ synchronized public void unsetPublicized() {
+ ensureMutableOI().unsetPublicized();
+ }
+
synchronized public void markUseIdentifierNameString() {
ensureMutableOI().markUseIdentifierNameString();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index feef8f7..91b1dc7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -729,8 +729,10 @@
}
public DexCallSite createCallSite(
- DexString methodName, DexProto methodProto,
- DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
+ DexString methodName,
+ DexProto methodProto,
+ DexMethodHandle bootstrapMethod,
+ List<DexValue> bootstrapArgs) {
// Call sites are never equal and therefore we do not canonicalize.
assert !sorted;
DexCallSite callSite = new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs);
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 17f6efa..5c05605 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -8,6 +8,8 @@
public class DexProto extends IndexedDexItem implements PresortedComparable<DexProto> {
+ public static final DexProto SENTINEL = new DexProto(null, null, null);
+
public final DexString shorty;
public final DexType returnType;
public final DexTypeList parameters;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 7d17f11..8de4a6e 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -110,6 +110,16 @@
public abstract DexField lookupField(DexField field);
+ // The method lookupMethod() maps a pair INVOKE=(method signature, invoke type) to a new pair
+ // INVOKE'=(method signature', invoke type'). This mapping can be context sensitive, meaning that
+ // the result INVOKE' depends on where the invocation INVOKE is in the program. This is, for
+ // example, used by the vertical class merger to translate invoke-super instructions that hit
+ // a method in the direct super class to invoke-direct instructions after class merging.
+ //
+ // This method can be used to determine if a graph lense is context sensitive. If a graph lense
+ // is context insensitive, it is safe to invoke lookupMethod() without a context (or to pass null
+ // as context). Trying to invoke a context sensitive graph lense without a context will lead to
+ // an assertion error.
public abstract boolean isContextFreeForMethods();
public boolean isContextFreeForMethod(DexMethod method) {
@@ -230,7 +240,7 @@
// TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
// that only subclasses which are known to need it actually do it?
return new GraphLenseLookupResult(
- newMethod, mapInvocationType(newMethod, method, context, type));
+ newMethod, mapInvocationType(newMethod, method, context, previous.getType()));
}
/**
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 640172f..2c37c6a 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -10,9 +10,12 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.JarSourceCode;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.jar.InliningConstraintVisitor;
import com.android.tools.r8.jar.JarRegisterEffectsVisitor;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.io.PrintWriter;
@@ -27,6 +30,7 @@
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;
@@ -169,6 +173,31 @@
DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
}
+ public Constraint computeInliningConstraint(
+ DexEncodedMethod encodedMethod,
+ AppInfoWithLiveness appInfo,
+ GraphLense graphLense,
+ DexType invocationContext) {
+ InliningConstraintVisitor visitor =
+ new InliningConstraintVisitor(
+ application, appInfo, graphLense, encodedMethod, invocationContext);
+ AbstractInsnNode insn = node.instructions.getFirst();
+ while (insn != null) {
+ insn.accept(visitor);
+ if (visitor.isFinished()) {
+ return visitor.getConstraint();
+ }
+ insn = insn.getNext();
+ }
+ for (TryCatchBlockNode block : node.tryCatchBlocks) {
+ visitor.accept(block);
+ if (visitor.isFinished()) {
+ return visitor.getConstraint();
+ }
+ }
+ return visitor.getConstraint();
+ }
+
@Override
public String toString() {
triggerDelayedParsingIfNeccessary();
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index 4530040..6948a08 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
public class AlwaysMaterializingUser extends Instruction {
@@ -58,8 +58,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forAlwaysMaterializingUser();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index f724a52..cff655a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
/**
@@ -76,8 +76,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forArgument();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index e755759..01101f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -22,8 +22,8 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Arrays;
import java.util.function.Function;
@@ -133,8 +133,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forArrayGet();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 9a170a6..cc358f0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -14,8 +14,8 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.function.Function;
public class ArrayLength extends Instruction {
@@ -90,8 +90,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forArrayLength();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index a721955..023afce 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -18,8 +18,8 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Arrays;
@@ -154,8 +154,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forArrayPut();
}
@Override
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 819c6f2..5bed061 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
@@ -659,6 +659,7 @@
for (Instruction instruction : getInstructions()) {
if (instruction.outValue != null) {
instruction.outValue.clearUsers();
+ instruction.setOutValue(null);
}
for (Value value : instruction.inValues) {
value.removeUser(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 58b61af..1adcd11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -390,7 +390,7 @@
ImmutableList<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
if (normalExits.isEmpty()) {
assert inlineeCanThrow;
- // TODO(sgjesse): Remove this restriction.
+ // TODO(sgjesse): Remove this restriction (see b/64432527).
assert !invokeBlock.hasCatchHandlers();
blocksToRemove.addAll(
invokePredecessor.unlink(invokeBlock, new DominatorTree(code)));
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index 9e6738a..e5190bb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -13,8 +13,8 @@
import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.function.Function;
public abstract class Binop extends Instruction {
@@ -124,8 +124,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forBinop();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 51e1891..dba575f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -16,7 +16,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
public class CheckCast extends Instruction {
@@ -113,8 +113,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, info);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forCheckCast(type, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 35549fb..3041aaf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import java.util.function.Function;
@@ -99,8 +99,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, clazz, info);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forConstClass(clazz, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java b/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
index cb9ef18..f6e212d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
@@ -5,7 +5,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
public abstract class ConstInstruction extends Instruction {
@@ -29,7 +29,8 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forConstInstruction();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 9e1c21d..637faf7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
public class DebugLocalRead extends Instruction {
@@ -60,8 +60,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forDebugLocalRead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index edf68f3..53791e5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -100,8 +100,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forDebugLocalsChange();
}
public boolean apply(Int2ReferenceMap<DebugLocalInfo> locals) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index f778037..aed2128 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
public class DebugPosition extends Instruction {
@@ -57,8 +57,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forDebugPosition();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 997a8c9..d88ca97 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -3,13 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.List;
public abstract class FieldInstruction extends Instruction {
@@ -50,27 +44,4 @@
public FieldInstruction asFieldInstruction() {
return this;
}
-
- /**
- * Returns the target of this field instruction, if such target is known, or null.
- * <p>
- * A result of null indicates that the field is either undefined or not of the right kind.
- */
- abstract DexEncodedField lookupTarget(DexType type, AppInfo appInfo);
-
- @Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- // Resolve the field if possible and decide whether the instruction can inlined.
- DexType fieldHolder = field.getHolder();
- DexEncodedField target = lookupTarget(fieldHolder, info);
- DexClass fieldClass = info.definitionFor(fieldHolder);
- if ((target != null) && (fieldClass != null)) {
- Constraint fieldConstraint = Constraint
- .deriveConstraint(invocationContext, fieldHolder, target.accessFlags, info);
- Constraint classConstraint = Constraint
- .deriveConstraint(invocationContext, fieldHolder, fieldClass.accessFlags, info);
- return Constraint.min(fieldConstraint, classConstraint);
- }
- return Constraint.NEVER;
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 08f3b8f..1083669 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -17,12 +17,13 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
import org.objectweb.asm.Opcodes;
@@ -113,8 +114,9 @@
}
@Override
- DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
- return appInfo.lookupInstanceTarget(type, field);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInstanceGet(field, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 6542e99..53fd31c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
public class InstanceOf extends Instruction {
@@ -81,8 +81,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, info);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInstanceOf(type, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index ededf98..3585348 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -15,12 +15,12 @@
import com.android.tools.r8.code.IputWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.Arrays;
import org.objectweb.asm.Opcodes;
@@ -113,8 +113,9 @@
}
@Override
- DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
- return appInfo.lookupInstanceTarget(type, field);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInstancePut(field, invocationContext);
}
@Override
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 62e518d..eb85222 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
@@ -17,8 +17,8 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
@@ -1056,11 +1056,11 @@
/**
* Returns the inlining constraint for this method when used in the context of the given type.
- * <p>
- * The type is used to judge visibility constraints and also for dispatch decisions.
+ *
+ * <p>The type is used to judge visibility constraints and also for dispatch decisions.
*/
- public abstract Constraint inliningConstraint(AppInfoWithLiveness info,
- DexType invocationContext);
+ public abstract Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext);
public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 0579362..a6e6fd1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.List;
public final class InvokeCustom extends Invoke {
@@ -101,8 +101,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.NEVER;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInvokeCustom();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index b343de7..88716dc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
import java.util.Collections;
@@ -112,8 +113,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return inliningConstraintForSinlgeTargetInvoke(info, invocationContext);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInvokeDirect(getInvokedMethod(), invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index ff8f0d0..06d368d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
import java.util.List;
@@ -94,8 +95,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return inliningConstraintForVirtualInvoke(info, invocationContext);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInvokeInterface(getInvokedMethod(), invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 5fe8972..a8ec608 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -5,15 +5,12 @@
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -85,86 +82,6 @@
return lookupSingleTarget(appInfo, appInfo.dexItemFactory.objectType);
}
- @Override
- public abstract Constraint inliningConstraint(AppInfoWithLiveness info,
- DexType invocationContext);
-
- protected Constraint inliningConstraintForSinlgeTargetInvoke(AppInfoWithLiveness info,
- DexType invocationContext) {
- if (method.holder.isArrayType()) {
- return Constraint.ALWAYS;
- }
- DexEncodedMethod target = lookupSingleTarget(info, invocationContext);
- if (target != null) {
- DexType methodHolder = target.method.holder;
- DexClass methodClass = info.definitionFor(methodHolder);
- if ((methodClass != null)) {
- Constraint methodConstraint = Constraint
- .deriveConstraint(invocationContext, methodHolder, target.accessFlags, info);
- // We also have to take the constraint of the enclosing class into account.
- Constraint classConstraint = Constraint
- .deriveConstraint(invocationContext, methodHolder, methodClass.accessFlags, info);
- return Constraint.min(methodConstraint, classConstraint);
- }
- }
- return Constraint.NEVER;
- }
-
- protected Constraint inliningConstraintForVirtualInvoke(AppInfoWithSubtyping info,
- DexType invocationContext) {
- if (method.holder.isArrayType()) {
- return Constraint.ALWAYS;
- }
- Collection<DexEncodedMethod> targets = lookupTargets(info, invocationContext);
- if (targets == null) {
- return Constraint.NEVER;
- }
-
- Constraint result = Constraint.ALWAYS;
-
- // Perform resolution and derive inlining constraints based on the accessibility of the
- // resolution result.
- ResolutionResult resolutionResult = info.resolveMethod(method.holder, method);
- DexEncodedMethod resolutionTarget = resolutionResult.asResultOfResolve();
- if (resolutionTarget == null) {
- // This will fail at runtime.
- return Constraint.NEVER;
- }
- DexType methodHolder = resolutionTarget.method.holder;
- DexClass methodClass = info.definitionFor(methodHolder);
- assert methodClass != null;
- Constraint methodConstraint = Constraint
- .deriveConstraint(invocationContext, methodHolder, resolutionTarget.accessFlags, info);
- result = Constraint.min(result, methodConstraint);
- // We also have to take the constraint of the enclosing class of the resolution result
- // into account. We do not allow inlining this method if it is calling something that
- // is inaccessible. Inlining in that case could move the code to another package making a
- // call succeed that should not succeed. Conversely, if the resolution result is accessible,
- // we have to make sure that inlining cannot make it inaccessible.
- Constraint classConstraint = Constraint
- .deriveConstraint(invocationContext, methodHolder, methodClass.accessFlags, info);
- result = Constraint.min(result, classConstraint);
- if (result == Constraint.NEVER) {
- return result;
- }
-
- // For each of the actual potential targets, derive constraints based on the accessibility
- // of the method itself.
- for (DexEncodedMethod target : targets) {
- methodHolder = target.method.holder;
- methodClass = info.definitionFor(methodHolder);
- assert methodClass != null;
- methodConstraint = Constraint
- .deriveConstraint(invocationContext, methodHolder, target.accessFlags, info);
- result = Constraint.min(result, methodConstraint);
- if (result == Constraint.NEVER) {
- return result;
- }
- }
-
- return result;
- }
-
public abstract InlineAction computeInlining(InliningOracle decider, DexType invocationContext);
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index d4f6bd2..e34d25f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.List;
import java.util.function.Function;
@@ -69,8 +69,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, info);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInvokeMultiNewArray(type, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 96680b3..a2a9c36 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.List;
import java.util.function.Function;
@@ -99,8 +99,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, info);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInvokeNewArray(type, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 81d4cdc..8f90630 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
@@ -125,8 +126,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.NEVER;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInvokePolymorphic(getInvokedMethod(), invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 94df8d3..d2183c0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
@@ -102,8 +103,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return inliningConstraintForSinlgeTargetInvoke(info, invocationContext);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInvokeStatic(getInvokedMethod(), invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index e5bd604..920d94b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
import java.util.Collections;
@@ -111,8 +112,8 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- // The semantics of invoke super depend on the context.
- return Constraint.SAMECLASS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInvokeSuper(getInvokedMethod(), invocationContext);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index d104270..f55a407 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
import java.util.List;
@@ -94,8 +95,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return inliningConstraintForVirtualInvoke(info, invocationContext);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forInvokeVirtual(getInvokedMethod(), invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index db151c6..6096292 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -5,7 +5,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
@@ -47,8 +47,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forJumpInstruction();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 10f4dad..32654b8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
public class Load extends Instruction {
@@ -51,8 +51,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forLoad();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 97d6e01..6b3c89c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
public class Monitor extends Instruction {
@@ -89,9 +89,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- // Conservative choice.
- return Constraint.NEVER;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forMonitor();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index b095bb4..e474bb8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
public class Move extends Instruction {
@@ -100,8 +100,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forMove();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index b7f0138..eabf7b2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import java.util.HashSet;
import java.util.List;
@@ -75,9 +75,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- // TODO(64432527): Revisit this constraint.
- return Constraint.NEVER;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forMoveException();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 3a69ac5..edca979 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
public class NewArrayEmpty extends Instruction {
@@ -84,8 +84,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, info);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forNewArrayEmpty(type, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index f203597..ac46cc7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Arrays;
@@ -112,8 +112,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forNewArrayFilledData();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index a5599fa..75931ad 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
public class NewInstance extends Instruction {
@@ -81,8 +81,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, clazz, info);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forNewInstance(clazz, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NonNull.java b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
index 0cf0616..462af06 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NonNull.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
public class NonNull extends Instruction {
@@ -85,8 +85,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forNonNull();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 70d6af4..0fb1949 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
public class Pop extends Instruction {
@@ -50,8 +50,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forPop();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 6d3a85b..37cfaf0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -16,7 +16,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
public class Return extends JumpInstruction {
@@ -116,8 +116,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forReturn();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 1e4d125..486f9e2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -16,12 +16,13 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
import org.objectweb.asm.Opcodes;
@@ -108,8 +109,9 @@
}
@Override
- DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
- return appInfo.lookupStaticTarget(type, field);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forStaticGet(field, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index df42f42..f910ff6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -14,12 +14,12 @@
import com.android.tools.r8.code.SputWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import org.objectweb.asm.Opcodes;
public class StaticPut extends FieldInstruction {
@@ -107,8 +107,9 @@
}
@Override
- DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
- return appInfo.lookupStaticTarget(type, field);
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forStaticPut(field, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index ba5a502..b81af69 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
public class Store extends Instruction {
@@ -53,8 +53,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forStore();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index 5d8a4e7..4295dbe 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
public class Throw extends JumpInstruction {
@@ -65,8 +65,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forThrow();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index 6db3ad5..2f6ed7f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
abstract public class Unop extends Instruction {
@@ -48,8 +48,9 @@
}
@Override
- public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
- return Constraint.ALWAYS;
+ public Constraint inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forUnop();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 53bbb7e..23d9a73 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -959,7 +959,7 @@
private Invoke.Type invokeType(MethodInsnNode method) {
switch (method.getOpcode()) {
case Opcodes.INVOKEVIRTUAL:
- if (isCallToPolymorphicSignatureMethod(method)) {
+ if (isCallToPolymorphicSignatureMethod(method.owner, method.name)) {
return Invoke.Type.POLYMORPHIC;
}
return Invoke.Type.VIRTUAL;
@@ -2954,17 +2954,17 @@
return writer.toString();
}
- private boolean isCallToPolymorphicSignatureMethod(MethodInsnNode method) {
- if (method.owner.equals("java/lang/invoke/MethodHandle")) {
- switch (method.name) {
+ public static boolean isCallToPolymorphicSignatureMethod(String owner, String name) {
+ if (owner.equals("java/lang/invoke/MethodHandle")) {
+ switch (name) {
case "invoke":
case "invokeExact":
return true;
default :
return false;
}
- } else if (method.owner.equals("java/lang/invoke/VarHandle")) {
- switch (method.name) {
+ } else if (owner.equals("java/lang/invoke/VarHandle")) {
+ switch (name) {
case "compareAndExchange":
case "compareAndExchangeAcquire":
case "compareAndExchangeRelease":
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index b7653fa..94bf56c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -98,9 +98,10 @@
if (target == null) {
target = appInfo.lookupDirectTarget(method);
}
- assert target == null ||
- (implHandle.type.isInvokeInstance() && isInstanceMethod(target)) ||
- (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target));
+ assert target == null
+ || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
+ || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
+ || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
return target;
}
@@ -129,12 +130,17 @@
private boolean isInstanceMethod(DexEncodedMethod encodedMethod) {
assert encodedMethod != null;
- return !encodedMethod.accessFlags.isConstructor() && !encodedMethod.accessFlags.isStatic();
+ return !encodedMethod.accessFlags.isConstructor() && !encodedMethod.isStaticMethod();
}
private boolean isPrivateInstanceMethod(DexEncodedMethod encodedMethod) {
assert encodedMethod != null;
- return encodedMethod.accessFlags.isPrivate() && isInstanceMethod(encodedMethod);
+ return encodedMethod.isPrivateMethod() && isInstanceMethod(encodedMethod);
+ }
+
+ private boolean isPublicizedInstanceMethod(DexEncodedMethod encodedMethod) {
+ assert encodedMethod != null;
+ return encodedMethod.isPublicized() && isInstanceMethod(encodedMethod);
}
final MethodAccessFlags getAccessibility() {
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 ee64165..121f47d 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
@@ -3033,10 +3033,8 @@
if (!value.isPhi()
&& value.definition.isNumberConversion()
&& value.definition.asNumberConversion().to == NumericType.DOUBLE) {
- Value newValue = code.createValue(
- instruction.outValue().outType(), instruction.getLocalInfo());
InvokeStatic invokeIsNaN =
- new InvokeStatic(javaLangDoubleisNaN.get(), newValue, ImmutableList.of(value));
+ new InvokeStatic(javaLangDoubleisNaN.get(), null, ImmutableList.of(value));
invokeIsNaN.setPosition(instruction.getPosition());
// Insert the invoke before the current instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index cc7f571..631f234 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -91,7 +91,9 @@
}
private Reason computeInliningReason(DexEncodedMethod target) {
- if (target.getOptimizationInfo().forceInline()) {
+ if (target.getOptimizationInfo().forceInline()
+ || (inliner.appInfo.hasLiveness()
+ && inliner.appInfo.withLiveness().forceInline.contains(target))) {
return Reason.FORCE;
}
if (inliner.appInfo.hasLiveness()
@@ -252,7 +254,7 @@
public InlineAction computeForInvokeWithReceiver(
InvokeMethodWithReceiver invoke, DexType invocationContext) {
DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
- if (candidate == null || inliner.isBlackListed(candidate.method)) {
+ if (candidate == null || inliner.isBlackListed(candidate)) {
return null;
}
@@ -268,6 +270,7 @@
if (info != null) {
info.exclude(invoke, "receiver for candidate can be null");
}
+ assert !inliner.appInfo.forceInline.contains(candidate.method);
return null;
}
@@ -293,7 +296,7 @@
@Override
public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
- if (candidate == null || inliner.isBlackListed(candidate.method)) {
+ if (candidate == null || inliner.isBlackListed(candidate)) {
return null;
}
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 44ebdc7..db674bd 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
@@ -70,14 +70,14 @@
blackList.add(appInfo.dexItemFactory.kotlin.intrinsics.throwNpe);
}
- public boolean isBlackListed(DexMethod method) {
- return blackList.contains(method);
+ public boolean isBlackListed(DexEncodedMethod method) {
+ return blackList.contains(method.method) || appInfo.neverInline.contains(method);
}
private Constraint instructionAllowedForInlining(
- DexEncodedMethod method, Instruction instruction) {
- Constraint result = instruction.inliningConstraint(appInfo, method.method.holder);
- if ((result == Constraint.NEVER) && instruction.isDebugInstruction()) {
+ Instruction instruction, InliningConstraints inliningConstraints, DexType invocationContext) {
+ Constraint result = instruction.inliningConstraint(inliningConstraints, invocationContext);
+ if (result == Constraint.NEVER && instruction.isDebugInstruction()) {
return Constraint.ALWAYS;
}
return result;
@@ -85,10 +85,12 @@
public Constraint computeInliningConstraint(IRCode code, DexEncodedMethod method) {
Constraint result = Constraint.ALWAYS;
+ InliningConstraints inliningConstraints = new InliningConstraints(appInfo);
InstructionIterator it = code.instructionIterator();
while (it.hasNext()) {
Instruction instruction = it.next();
- Constraint state = instructionAllowedForInlining(method, instruction);
+ Constraint state =
+ instructionAllowedForInlining(instruction, inliningConstraints, method.method.holder);
result = Constraint.min(result, state);
if (result == Constraint.NEVER) {
break;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
new file mode 100644
index 0000000..073d6ae
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -0,0 +1,334 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
+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.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import java.util.Collection;
+
+// Computes the inlining constraint for a given instruction.
+public class InliningConstraints {
+
+ private AppInfoWithLiveness appInfo;
+
+ // Currently used only by the vertical class merger (in all other cases this is the identity).
+ //
+ // When merging a type A into its subtype B we need to inline A.<init>() into B.<init>().
+ // Therefore, we need to be sure that A.<init>() can in fact be inlined into B.<init>() *before*
+ // we merge the two classes. However, at this point, we may reject the method A.<init>() from
+ // being inlined into B.<init>() only because it is not declared in the same class as B (which
+ // it would be after merging A and B).
+ //
+ // To circumvent this problem, the vertical class merger creates a graph lense that maps the
+ // type A to B, to create a temporary view of what the world would look like after class merging.
+ private GraphLense graphLense;
+
+ public InliningConstraints(AppInfoWithLiveness appInfo) {
+ this(appInfo, GraphLense.getIdentityLense());
+ }
+
+ public InliningConstraints(AppInfoWithLiveness appInfo, GraphLense graphLense) {
+ assert graphLense.isContextFreeForMethods();
+ this.appInfo = appInfo;
+ this.graphLense = graphLense;
+ }
+
+ public Constraint forAlwaysMaterializingUser() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forArgument() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forArrayGet() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forArrayLength() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forArrayPut() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forBinop() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forCheckCast(DexType type, DexType invocationContext) {
+ return Constraint.classIsVisible(invocationContext, type, appInfo);
+ }
+
+ public Constraint forConstClass(DexType type, DexType invocationContext) {
+ return Constraint.classIsVisible(invocationContext, type, appInfo);
+ }
+
+ public Constraint forConstInstruction() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forDebugLocalRead() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forDebugLocalsChange() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forDebugPosition() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forInstanceGet(DexField field, DexType invocationContext) {
+ DexField lookup = graphLense.lookupField(field);
+ return forFieldInstruction(
+ lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext);
+ }
+
+ public Constraint forInstanceOf(DexType type, DexType invocationContext) {
+ return Constraint.classIsVisible(invocationContext, type, appInfo);
+ }
+
+ public Constraint forInstancePut(DexField field, DexType invocationContext) {
+ DexField lookup = graphLense.lookupField(field);
+ return forFieldInstruction(
+ lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext);
+ }
+
+ public Constraint forInvoke(DexMethod method, Type type, DexType invocationContext) {
+ switch (type) {
+ case DIRECT:
+ return forInvokeDirect(method, invocationContext);
+ case INTERFACE:
+ return forInvokeInterface(method, invocationContext);
+ case STATIC:
+ return forInvokeStatic(method, invocationContext);
+ case SUPER:
+ return forInvokeSuper(method, invocationContext);
+ case VIRTUAL:
+ return forInvokeVirtual(method, invocationContext);
+ case CUSTOM:
+ return forInvokeCustom();
+ case POLYMORPHIC:
+ return forInvokePolymorphic(method, invocationContext);
+ default:
+ throw new Unreachable("Unexpected type: " + type);
+ }
+ }
+
+ public Constraint forInvokeCustom() {
+ return Constraint.NEVER;
+ }
+
+ public Constraint forInvokeDirect(DexMethod method, DexType invocationContext) {
+ DexMethod lookup = graphLense.lookupMethod(method);
+ return forSingleTargetInvoke(lookup, appInfo.lookupDirectTarget(lookup), invocationContext);
+ }
+
+ public Constraint forInvokeInterface(DexMethod method, DexType invocationContext) {
+ DexMethod lookup = graphLense.lookupMethod(method);
+ return forVirtualInvoke(lookup, appInfo.lookupInterfaceTargets(lookup), invocationContext);
+ }
+
+ public Constraint forInvokeMultiNewArray(DexType type, DexType invocationContext) {
+ return Constraint.classIsVisible(invocationContext, type, appInfo);
+ }
+
+ public Constraint forInvokeNewArray(DexType type, DexType invocationContext) {
+ return Constraint.classIsVisible(invocationContext, type, appInfo);
+ }
+
+ public Constraint forInvokePolymorphic(DexMethod method, DexType invocationContext) {
+ return Constraint.NEVER;
+ }
+
+ public Constraint forInvokeStatic(DexMethod method, DexType invocationContext) {
+ DexMethod lookup = graphLense.lookupMethod(method);
+ return forSingleTargetInvoke(lookup, appInfo.lookupStaticTarget(lookup), invocationContext);
+ }
+
+ public Constraint forInvokeSuper(DexMethod method, DexType invocationContext) {
+ // The semantics of invoke super depend on the context.
+ return Constraint.SAMECLASS;
+ }
+
+ public Constraint forInvokeVirtual(DexMethod method, DexType invocationContext) {
+ DexMethod lookup = graphLense.lookupMethod(method);
+ return forVirtualInvoke(lookup, appInfo.lookupVirtualTargets(lookup), invocationContext);
+ }
+
+ public Constraint forJumpInstruction() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forLoad() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forMonitor() {
+ // Conservative choice.
+ return Constraint.NEVER;
+ }
+
+ public Constraint forMove() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forMoveException() {
+ // TODO(64432527): Revisit this constraint.
+ return Constraint.NEVER;
+ }
+
+ public Constraint forNewArrayEmpty(DexType type, DexType invocationContext) {
+ return Constraint.classIsVisible(invocationContext, type, appInfo);
+ }
+
+ public Constraint forNewArrayFilledData() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forNewInstance(DexType type, DexType invocationContext) {
+ return Constraint.classIsVisible(invocationContext, type, appInfo);
+ }
+
+ public Constraint forNonNull() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forPop() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forReturn() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forStaticGet(DexField field, DexType invocationContext) {
+ DexField lookup = graphLense.lookupField(field);
+ return forFieldInstruction(
+ lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), invocationContext);
+ }
+
+ public Constraint forStaticPut(DexField field, DexType invocationContext) {
+ DexField lookup = graphLense.lookupField(field);
+ return forFieldInstruction(
+ lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), invocationContext);
+ }
+
+ public Constraint forStore() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forThrow() {
+ return Constraint.ALWAYS;
+ }
+
+ public Constraint forUnop() {
+ return Constraint.ALWAYS;
+ }
+
+ private Constraint forFieldInstruction(
+ DexField field, DexEncodedField target, DexType invocationContext) {
+ // Resolve the field if possible and decide whether the instruction can inlined.
+ DexType fieldHolder = graphLense.lookupType(field.clazz);
+ DexClass fieldClass = appInfo.definitionFor(fieldHolder);
+ if (target != null && fieldClass != null) {
+ Constraint fieldConstraint =
+ Constraint.deriveConstraint(invocationContext, fieldHolder, target.accessFlags, appInfo);
+ Constraint classConstraint =
+ Constraint.deriveConstraint(
+ invocationContext, fieldHolder, fieldClass.accessFlags, appInfo);
+ return Constraint.min(fieldConstraint, classConstraint);
+ }
+ return Constraint.NEVER;
+ }
+
+ private Constraint forSingleTargetInvoke(
+ DexMethod method, DexEncodedMethod target, DexType invocationContext) {
+ if (method.holder.isArrayType()) {
+ return Constraint.ALWAYS;
+ }
+ if (target != null) {
+ DexType methodHolder = graphLense.lookupType(target.method.holder);
+ DexClass methodClass = appInfo.definitionFor(methodHolder);
+ if (methodClass != null) {
+ Constraint methodConstraint =
+ Constraint.deriveConstraint(
+ invocationContext, methodHolder, target.accessFlags, appInfo);
+ // We also have to take the constraint of the enclosing class into account.
+ Constraint classConstraint =
+ Constraint.deriveConstraint(
+ invocationContext, methodHolder, methodClass.accessFlags, appInfo);
+ return Constraint.min(methodConstraint, classConstraint);
+ }
+ }
+ return Constraint.NEVER;
+ }
+
+ private Constraint forVirtualInvoke(
+ DexMethod method, Collection<DexEncodedMethod> targets, DexType invocationContext) {
+ if (method.holder.isArrayType()) {
+ return Constraint.ALWAYS;
+ }
+ if (targets == null) {
+ return Constraint.NEVER;
+ }
+
+ // Perform resolution and derive inlining constraints based on the accessibility of the
+ // resolution result.
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ DexEncodedMethod resolutionTarget = resolutionResult.asResultOfResolve();
+ if (resolutionTarget == null) {
+ // This will fail at runtime.
+ return Constraint.NEVER;
+ }
+
+ DexType methodHolder = graphLense.lookupType(resolutionTarget.method.holder);
+ DexClass methodClass = appInfo.definitionFor(methodHolder);
+ assert methodClass != null;
+ Constraint methodConstraint =
+ Constraint.deriveConstraint(
+ invocationContext, methodHolder, resolutionTarget.accessFlags, appInfo);
+ // We also have to take the constraint of the enclosing class of the resolution result
+ // into account. We do not allow inlining this method if it is calling something that
+ // is inaccessible. Inlining in that case could move the code to another package making a
+ // call succeed that should not succeed. Conversely, if the resolution result is accessible,
+ // we have to make sure that inlining cannot make it inaccessible.
+ Constraint classConstraint =
+ Constraint.deriveConstraint(
+ invocationContext, methodHolder, methodClass.accessFlags, appInfo);
+ Constraint result = Constraint.min(methodConstraint, classConstraint);
+ if (result == Constraint.NEVER) {
+ return result;
+ }
+
+ // For each of the actual potential targets, derive constraints based on the accessibility
+ // of the method itself.
+ for (DexEncodedMethod target : targets) {
+ methodHolder = graphLense.lookupType(target.method.holder);
+ assert appInfo.definitionFor(methodHolder) != null;
+ methodConstraint =
+ Constraint.deriveConstraint(invocationContext, methodHolder, target.accessFlags, appInfo);
+ result = Constraint.min(result, methodConstraint);
+ if (result == Constraint.NEVER) {
+ return result;
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index e749479..c3a3781 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -107,6 +107,7 @@
final private AppInfoWithLiveness appInfo;
final private DexItemFactory dexItemFactory;
+ private final InliningConstraints inliningConstraints;
// Representation of an outline.
// This includes the instructions in the outline, and a map from the arguments of this outline
@@ -475,7 +476,7 @@
// See whether we could move this invoke somewhere else. We reuse the logic from inlining
// here, as the constraints are the same.
- Constraint constraint = invoke.inliningConstraint(appInfo, method.method.holder);
+ Constraint constraint = invoke.inliningConstraint(inliningConstraints, method.method.holder);
if (constraint != Constraint.ALWAYS) {
return false;
}
@@ -818,6 +819,7 @@
public Outliner(AppInfoWithLiveness appInfo, InternalOptions options) {
this.appInfo = appInfo;
this.dexItemFactory = appInfo.dexItemFactory;
+ this.inliningConstraints = new InliningConstraints(appInfo);
this.options = options;
}
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 8556905..b3bde90 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
@@ -444,6 +444,12 @@
return null;
}
+ // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+ if (initInvoke.getBlock().hasCatchHandlers() &&
+ definition.getOptimizationInfo().neverReturnsNormally()) {
+ return null;
+ }
+
// If the superclass of the initializer is NOT java.lang.Object, the super class
// initializer being called must be classified as TrivialInstanceInitializer.
//
@@ -476,16 +482,16 @@
if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
return null; // Instance passed as an argument.
}
- return isEligibleMethodCall(invoke.getInvokedMethod(),
+ return isEligibleMethodCall(!invoke.getBlock().hasCatchHandlers(), invoke.getInvokedMethod(),
eligibility -> !eligibility.returnsReceiver ||
invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0);
}
private InliningInfo isEligibleIndirectMethodCall(DexMethod callee) {
- return isEligibleMethodCall(callee, eligibility -> !eligibility.returnsReceiver);
+ return isEligibleMethodCall(false, callee, eligibility -> !eligibility.returnsReceiver);
}
- private InliningInfo isEligibleMethodCall(
+ private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
DexEncodedMethod singleTarget = findSingleTarget(callee);
@@ -496,8 +502,9 @@
return null; // Don't inline itself.
}
- ClassInlinerEligibility eligibility =
- singleTarget.getOptimizationInfo().getClassInlinerEligibility();
+ OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+
+ ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
if (eligibility == null) {
return null;
}
@@ -508,6 +515,11 @@
return null;
}
+ // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+ if (!allowMethodsWithoutNormalReturns && optimizationInfo.neverReturnsNormally()) {
+ return null;
+ }
+
if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
// We won't be able to inline it here.
@@ -570,6 +582,11 @@
OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+ // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+ if (invokeMethod.getBlock().hasCatchHandlers() && optimizationInfo.neverReturnsNormally()) {
+ return false;
+ }
+
// 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);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index cebcc8c..08d0cae 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -354,7 +354,8 @@
// avoid a bug where the index variable could end up being uninitialized.
if (code.options.canHaveBoundsCheckEliminationBug()
&& move.from.getValue().isConstNumber()
- && move.type == MoveType.SINGLE) {
+ && move.type == MoveType.SINGLE
+ && allocator.unadjustedRealRegisterFromAllocated(move.to.getRegister()) < 256) {
scheduler.addMove(
new RegisterMove(move.to.getRegister(), move.type, move.from.getValue().definition));
} else {
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
new file mode 100644
index 0000000..459d7de
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -0,0 +1,235 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.jar;
+
+import static org.objectweb.asm.Opcodes.ASM6;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.conversion.JarSourceCode;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.TryCatchBlockNode;
+
+// This visitor can be used to determine if a piece of jar code has any instructions that the
+// inliner would not be willing to inline. This can be used to determine if a method can be force
+// inlined although its IR is still not available.
+//
+// Note that this class has only been implemented for the hooks in InliningConstraints that may
+// return a non-ALWAYS inlining constraint (e.g., InliningConstraints.forReturn is not called).
+public class InliningConstraintVisitor extends MethodVisitor {
+
+ private final JarApplicationReader application;
+ private final AppInfoWithLiveness appInfo;
+ private final GraphLense graphLense;
+ private final InliningConstraints inliningConstraints;
+ private final DexEncodedMethod method;
+ private final DexType invocationContext;
+
+ private Constraint constraint;
+
+ public InliningConstraintVisitor(
+ JarApplicationReader application,
+ AppInfoWithLiveness appInfo,
+ GraphLense graphLense,
+ DexEncodedMethod method,
+ DexType invocationContext) {
+ super(ASM6);
+ assert graphLense.isContextFreeForMethods();
+ this.application = application;
+ this.appInfo = appInfo;
+ this.graphLense = graphLense;
+ this.inliningConstraints = new InliningConstraints(appInfo, graphLense);
+ this.method = method;
+ this.invocationContext = invocationContext;
+
+ // Model a synchronized method as having a monitor instruction.
+ this.constraint =
+ method.accessFlags.isSynchronized() ? inliningConstraints.forMonitor() : Constraint.ALWAYS;
+ }
+
+ public Constraint getConstraint() {
+ return constraint;
+ }
+
+ private void updateConstraint(Constraint other) {
+ constraint = Constraint.min(constraint, other);
+ }
+
+ // Used to signal that the result is ready, such that we do not need to visit all instructions of
+ // the method, if we can see early on that it cannot be inlined anyway.
+ public boolean isFinished() {
+ return constraint == Constraint.NEVER;
+ }
+
+ public void accept(TryCatchBlockNode tryCatchBlock) {
+ // Model a try-catch as a move-exception instruction.
+ updateConstraint(inliningConstraints.forMoveException());
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ DexField field = application.getField(owner, name, desc);
+ switch (opcode) {
+ case Opcodes.GETFIELD:
+ updateConstraint(inliningConstraints.forInstanceGet(field, invocationContext));
+ break;
+
+ case Opcodes.PUTFIELD:
+ updateConstraint(inliningConstraints.forInstancePut(field, invocationContext));
+ break;
+
+ case Opcodes.GETSTATIC:
+ updateConstraint(inliningConstraints.forStaticGet(field, invocationContext));
+ break;
+
+ case Opcodes.PUTSTATIC:
+ updateConstraint(inliningConstraints.forStaticPut(field, invocationContext));
+ break;
+
+ default:
+ throw new Unreachable("Unexpected opcode " + opcode);
+ }
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type && ((Type) cst).getSort() != Type.METHOD) {
+ DexType type = application.getType((Type) cst);
+ updateConstraint(inliningConstraints.forConstClass(type, invocationContext));
+ } else {
+ updateConstraint(inliningConstraints.forConstInstruction());
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ DexType ownerType = application.getTypeFromName(owner);
+ DexMethod target = application.getMethod(ownerType, name, desc);
+
+ // Find the DEX invocation type.
+ Invoke.Type type;
+ switch (opcode) {
+ case Opcodes.INVOKEDYNAMIC:
+ type =
+ JarSourceCode.isCallToPolymorphicSignatureMethod(owner, name)
+ ? Invoke.Type.POLYMORPHIC
+ : Invoke.Type.CUSTOM;
+ assert noNeedToUseGraphLense(target, type);
+ break;
+
+ case Opcodes.INVOKEINTERFACE:
+ // Could have changed to an invoke-virtual instruction due to vertical class merging
+ // (if an interface is merged into a class).
+ type = graphLense.lookupMethod(target, null, Invoke.Type.INTERFACE).getType();
+ assert type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL;
+ break;
+
+ case Opcodes.INVOKESPECIAL:
+ if (name.equals(Constants.INSTANCE_INITIALIZER_NAME)) {
+ type = Invoke.Type.DIRECT;
+ assert noNeedToUseGraphLense(target, type);
+ } else if (ownerType == invocationContext) {
+ // The method could have been publicized.
+ type = graphLense.lookupMethod(target, null, Invoke.Type.DIRECT).getType();
+ assert type == Invoke.Type.DIRECT || type == Invoke.Type.VIRTUAL;
+ } else {
+ // This is a super call. Note that the vertical class merger translates some invoke-super
+ // instructions to invoke-direct. However, when that happens, the invoke instruction and
+ // the target method end up being in the same class, and therefore, we will allow inlining
+ // it. The result of using type=SUPER below will be the same, since it leads to the
+ // inlining constraint SAMECLASS.
+ // TODO(christofferqa): Consider using graphLense.lookupMethod (to do this, we need the
+ // context for the graph lense, though).
+ type = Invoke.Type.SUPER;
+ assert noNeedToUseGraphLense(target, type);
+ }
+ break;
+
+ case Opcodes.INVOKESTATIC:
+ type = Invoke.Type.STATIC;
+ assert noNeedToUseGraphLense(target, type);
+ break;
+
+ case Opcodes.INVOKEVIRTUAL:
+ type = Invoke.Type.VIRTUAL;
+ // Instructions that target a private method in the same class translates to invoke-direct.
+ if (target.holder == method.method.holder) {
+ DexClass clazz = appInfo.definitionFor(target.holder);
+ if (clazz != null && clazz.lookupDirectMethod(target) != null) {
+ type = Invoke.Type.DIRECT;
+ }
+ }
+ assert noNeedToUseGraphLense(target, type);
+ break;
+
+ default:
+ throw new Unreachable("Unexpected opcode " + opcode);
+ }
+
+ updateConstraint(inliningConstraints.forInvoke(target, type, invocationContext));
+ }
+
+ private boolean noNeedToUseGraphLense(DexMethod method, Invoke.Type type) {
+ assert graphLense.lookupMethod(method, null, type).getType() == type;
+ return true;
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ switch (opcode) {
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
+ updateConstraint(inliningConstraints.forMonitor());
+ break;
+
+ default:
+ // All instructions here lead to the inlining constraint ALWAYS.
+ }
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ DexType type = application.getTypeFromDescriptor(desc);
+ updateConstraint(inliningConstraints.forInvokeMultiNewArray(type, invocationContext));
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String typeName) {
+ DexType type = application.getTypeFromName(typeName);
+ switch (opcode) {
+ case Opcodes.ANEWARRAY:
+ updateConstraint(inliningConstraints.forNewArrayEmpty(type, invocationContext));
+ break;
+
+ case Opcodes.CHECKCAST:
+ updateConstraint(inliningConstraints.forCheckCast(type, invocationContext));
+ break;
+
+ case Opcodes.INSTANCEOF:
+ updateConstraint(inliningConstraints.forInstanceOf(type, invocationContext));
+ break;
+
+ case Opcodes.NEW:
+ updateConstraint(inliningConstraints.forNewInstance(type, invocationContext));
+ break;
+
+ default:
+ throw new Unreachable("Unexpected opcode " + opcode);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index f5d48fd..f114a7e 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
@@ -33,13 +34,10 @@
private final GraphLense previousLense;
private final SeedMapper seedMapper;
- public ProguardMapApplier(
- AppInfoWithLiveness appInfo,
- GraphLense previousLense,
- SeedMapper seedMapper) {
- assert previousLense.isContextFreeForMethods();
- this.appInfo = appInfo;
- this.previousLense = previousLense;
+ public ProguardMapApplier(AppView<AppInfoWithLiveness> appView, SeedMapper seedMapper) {
+ assert appView.getGraphLense().isContextFreeForMethods();
+ this.appInfo = appView.getAppInfo();
+ this.previousLense = appView.getGraphLense();
this.seedMapper = seedMapper;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index eaeb1ee..73ebf73 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize;
-import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,54 +18,47 @@
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.Sets;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
public final class ClassAndMemberPublicizer {
private final DexApplication application;
- private final AppInfo appInfo;
+ private final AppView appView;
private final RootSet rootSet;
- private final GraphLense previuosLense;
private final PublicizedLenseBuilder lenseBuilder;
private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
- // TODO(b/72109068): finer-grained naming spaces, e.g., per-tree.
- private final Set<Wrapper<DexMethod>> methodPool = Sets.newConcurrentHashSet();
+ private final Map<DexClass, MethodPool> methodPools = new ConcurrentHashMap<>();
- private ClassAndMemberPublicizer(
- DexApplication application,
- AppInfo appInfo,
- RootSet rootSet,
- GraphLense previousLense) {
+ private ClassAndMemberPublicizer(DexApplication application, AppView appView, RootSet rootSet) {
this.application = application;
- this.appInfo = appInfo;
+ this.appView = appView;
this.rootSet = rootSet;
- this.previuosLense = previousLense;
lenseBuilder = PublicizerLense.createBuilder();
}
/**
- * Marks all package private and protected methods and fields as public.
- * Makes all private static methods public.
- * Makes private instance methods public final instance methods, if possible.
- * <p>
- * This will destructively update the DexApplication passed in as argument.
+ * Marks all package private and protected methods and fields as public. Makes all private static
+ * methods public. Makes private instance methods public final instance methods, if possible.
+ *
+ * <p>This will destructively update the DexApplication passed in as argument.
*/
public static GraphLense run(
ExecutorService executorService,
Timing timing,
DexApplication application,
- AppInfo appInfo,
- RootSet rootSet,
- GraphLense previousLense) throws ExecutionException {
- return new ClassAndMemberPublicizer(application, appInfo, rootSet, previousLense)
- .run(executorService, timing);
+ AppView appView,
+ RootSet rootSet)
+ throws ExecutionException {
+ return new ClassAndMemberPublicizer(application, appView, rootSet).run(executorService, timing);
}
private GraphLense run(ExecutorService executorService, Timing timing)
@@ -74,9 +67,8 @@
timing.begin("Phase 1: collectMethods");
try {
List<Future<?>> futures = new ArrayList<>();
- // TODO(b/72109068): finer-grained naming spaces will need a different class visiting.
application.classes().forEach(clazz ->
- futures.add(executorService.submit(collectMethodPerClass(clazz))));
+ futures.add(executorService.submit(computeMethodPoolPerClass(clazz))));
ThreadUtils.awaitFutures(futures);
} finally {
timing.end();
@@ -84,21 +76,39 @@
// Phase 2: Visit classes and promote class/member to public if possible.
timing.begin("Phase 2: promoteToPublic");
- DexType.forAllInterfaces(appInfo.dexItemFactory, this::publicizeType);
- publicizeType(appInfo.dexItemFactory.objectType);
+ DexType.forAllInterfaces(appView.getDexItemFactory(), this::publicizeType);
+ publicizeType(appView.getDexItemFactory().objectType);
timing.end();
- return lenseBuilder.build(appInfo, previuosLense);
+ return lenseBuilder.build(appView);
}
- private Runnable collectMethodPerClass(DexClass clazz) {
+ private Runnable computeMethodPoolPerClass(DexClass clazz) {
return () -> {
+ MethodPool methodPool = methodPools.computeIfAbsent(clazz, k -> new MethodPool());
clazz.forEachMethod(encodedMethod -> {
// We will add private instance methods when we promote them.
if (!encodedMethod.isPrivateMethod() || encodedMethod.isStaticMethod()) {
- methodPool.add(equivalence.wrap(encodedMethod.method));
+ methodPool.seen(equivalence.wrap(encodedMethod.method));
}
});
+ if (clazz.superType != null) {
+ DexClass superClazz = application.definitionFor(clazz.superType);
+ if (superClazz != null) {
+ MethodPool superPool = methodPools.computeIfAbsent(superClazz, k -> new MethodPool());
+ superPool.linkSubtype(methodPool);
+ methodPool.linkSupertype(superPool);
+ }
+ }
+ if (clazz.isInterface()) {
+ clazz.type.forAllImplementsSubtypes(implementer -> {
+ DexClass subClazz = application.definitionFor(implementer);
+ if (subClazz != null) {
+ MethodPool childPool = methodPools.computeIfAbsent(subClazz, k -> new MethodPool());
+ childPool.linkInterface(methodPool);
+ }
+ });
+ }
};
}
@@ -118,7 +128,6 @@
}
}
- // TODO(b/72109068): Can process sub types in parallel.
type.forAllExtendsSubtypes(this::publicizeType);
}
@@ -128,7 +137,7 @@
return false;
}
- if (appInfo.dexItemFactory.isClassConstructor(encodedMethod.method)) {
+ if (appView.getDexItemFactory().isClassConstructor(encodedMethod.method)) {
return false;
}
@@ -139,7 +148,7 @@
}
assert accessFlags.isPrivate();
- if (appInfo.dexItemFactory.isConstructor(encodedMethod.method)) {
+ if (appView.getDexItemFactory().isConstructor(encodedMethod.method)) {
// TODO(b/72211928)
return false;
}
@@ -152,21 +161,22 @@
// We can't publicize private instance methods in interfaces or methods that are copied from
// interfaces to lambda-desugared classes because this will be added as a new default method.
- // TODO(b/72109068): It might be possible to transform it into static methods, though.
+ // TODO(b/111118390): It might be possible to transform it into static methods, though.
if (holder.isInterface() || accessFlags.isSynthetic()) {
return false;
}
+ MethodPool methodPool = methodPools.get(holder);
Wrapper<DexMethod> key = equivalence.wrap(encodedMethod.method);
- if (methodPool.contains(key)) {
+ if (methodPool.hasSeen(key)) {
// We can't do anything further because even renaming is not allowed due to the keep rule.
if (rootSet.noObfuscation.contains(encodedMethod)) {
return false;
}
- // TODO(b/72109068): Renaming will enable more private instance methods to be publicized.
+ // TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
return false;
}
- methodPool.add(key);
+ methodPool.seen(key);
lenseBuilder.add(encodedMethod.method);
accessFlags.unsetPrivate();
accessFlags.setFinal();
@@ -186,4 +196,51 @@
accessFlags.setPublic();
return false;
}
+
+ // Per-class collection of method signatures, which will be used to determine if a certain method
+ // can be publicized or not.
+ static class MethodPool {
+ private MethodPool superType;
+ private final Set<MethodPool> interfaces = new HashSet<>();
+ private final Set<MethodPool> subTypes = new HashSet<>();
+ private final Set<Wrapper<DexMethod>> methodPool = new HashSet<>();
+
+ MethodPool() {
+ }
+
+ synchronized void linkSupertype(MethodPool superType) {
+ assert this.superType == null;
+ this.superType = superType;
+ }
+
+ synchronized void linkSubtype(MethodPool subType) {
+ boolean added = subTypes.add(subType);
+ assert added;
+ }
+
+ synchronized void linkInterface(MethodPool itf) {
+ boolean added = interfaces.add(itf);
+ assert added;
+ }
+
+ synchronized void seen(Wrapper<DexMethod> method) {
+ boolean added = methodPool.add(method);
+ assert added;
+ }
+
+ boolean hasSeen(Wrapper<DexMethod> method) {
+ return hasSeenUpwardRecursive(method) || hasSeenDownwardRecursive(method);
+ }
+
+ private boolean hasSeenUpwardRecursive(Wrapper<DexMethod> method) {
+ return methodPool.contains(method)
+ || (superType != null && superType.hasSeenUpwardRecursive(method))
+ || interfaces.stream().anyMatch(itf -> itf.hasSeenUpwardRecursive(method));
+ }
+
+ private boolean hasSeenDownwardRecursive(Wrapper<DexMethod> method) {
+ return methodPool.contains(method)
+ || subTypes.stream().anyMatch(subType -> subType.hasSeenDownwardRecursive(method));
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 9efd5c8..fe098cc 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize;
+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;
@@ -28,10 +29,10 @@
private final GraphLense lense;
private final MemberRebindingLense.Builder builder;
- public MemberRebindingAnalysis(AppInfoWithLiveness appInfo, GraphLense lense) {
- assert lense.isContextFreeForMethods();
- this.appInfo = appInfo;
- this.lense = lense;
+ public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
+ assert appView.getGraphLense().isContextFreeForMethods();
+ this.appInfo = appView.getAppInfo();
+ this.lense = appView.getGraphLense();
this.builder = MemberRebindingLense.builder(appInfo);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
index 835135e..7a9c08f 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize;
-import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
@@ -15,16 +15,19 @@
import java.util.Set;
final class PublicizerLense extends NestedGraphLense {
- private final AppInfo appInfo;
+ private final AppView appView;
private final Set<DexMethod> publicizedMethods;
- PublicizerLense(
- AppInfo appInfo, GraphLense previousLense, Set<DexMethod> publicizedMethods) {
+ PublicizerLense(AppView appView, Set<DexMethod> publicizedMethods) {
// This lense does not map any DexItem's at all.
// It will just tweak invoke type for publicized methods from invoke-direct to invoke-virtual.
- super(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(),
- previousLense, appInfo.dexItemFactory);
- this.appInfo = appInfo;
+ super(
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ appView.getGraphLense(),
+ appView.getAppInfo().dexItemFactory);
+ this.appView = appView;
this.publicizedMethods = publicizedMethods;
}
@@ -35,18 +38,24 @@
method = previous.getMethod();
type = previous.getType();
if (type == Type.DIRECT && publicizedMethods.contains(method)) {
- DexClass holderClass = appInfo.definitionFor(method.holder);
- if (holderClass != null) {
- DexEncodedMethod actualEncodedTarget = holderClass.lookupVirtualMethod(method);
- if (actualEncodedTarget != null
- && actualEncodedTarget.isPublicized()) {
- return new GraphLenseLookupResult(method, Type.VIRTUAL);
- }
- }
+ assert publicizedMethodIsPresentOnHolder(method, context);
+ return new GraphLenseLookupResult(method, Type.VIRTUAL);
}
return super.lookupMethod(method, context, type);
}
+ private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexEncodedMethod context) {
+ GraphLenseLookupResult lookup =
+ appView.getGraphLense().lookupMethod(method, context, Type.VIRTUAL);
+ DexMethod signatureInCurrentWorld = lookup.getMethod();
+ DexClass clazz = appView.getAppInfo().definitionFor(signatureInCurrentWorld.holder);
+ assert clazz != null;
+ DexEncodedMethod actualEncodedTarget = clazz.lookupVirtualMethod(signatureInCurrentWorld);
+ assert actualEncodedTarget != null;
+ assert actualEncodedTarget.isPublicized();
+ return true;
+ }
+
static PublicizedLenseBuilder createBuilder() {
return new PublicizedLenseBuilder();
}
@@ -57,8 +66,8 @@
private PublicizedLenseBuilder() {
}
- public GraphLense build(AppInfo appInfo, GraphLense previousLense) {
- return new PublicizerLense(appInfo, previousLense, methodSetBuilder.build());
+ public GraphLense build(AppView appView) {
+ return new PublicizerLense(appView, methodSetBuilder.build());
}
public void add(DexMethod publicizedMethod) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 69e0221..061b9bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1551,10 +1551,18 @@
*/
public final Map<DexItem, ProguardMemberRule> assumedValues;
/**
- * All methods that have to be inlined due to a configuration directive.
+ * All methods that should be inlined if possible due to a configuration directive.
*/
public final Set<DexItem> alwaysInline;
/**
+ * All methods that *must* be inlined due to a configuration directive (testing only).
+ */
+ public final Set<DexItem> forceInline;
+ /**
+ * All methods that *must* never be inlined due to a configuration directive (testing only).
+ */
+ public final Set<DexItem> neverInline;
+ /**
* All items with -identifiernamestring rule.
*/
public final Set<DexItem> identifierNameStrings;
@@ -1609,6 +1617,8 @@
this.noSideEffects = enqueuer.rootSet.noSideEffects;
this.assumedValues = enqueuer.rootSet.assumedValues;
this.alwaysInline = enqueuer.rootSet.alwaysInline;
+ this.forceInline = enqueuer.rootSet.forceInline;
+ this.neverInline = enqueuer.rootSet.neverInline;
this.identifierNameStrings =
Sets.union(enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
this.protoLiteFields = enqueuer.protoLiteFields;
@@ -1647,6 +1657,8 @@
this.brokenSuperInvokes = previous.brokenSuperInvokes;
this.protoLiteFields = previous.protoLiteFields;
this.alwaysInline = previous.alwaysInline;
+ this.forceInline = previous.forceInline;
+ this.neverInline = previous.neverInline;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
this.switchMaps = previous.switchMaps;
@@ -1690,6 +1702,8 @@
this.assumedValues = previous.assumedValues;
assert lense.assertNotModified(previous.alwaysInline);
this.alwaysInline = previous.alwaysInline;
+ this.forceInline = previous.forceInline;
+ this.neverInline = previous.neverInline;
this.identifierNameStrings =
rewriteMixedItemsConservatively(previous.identifierNameStrings, lense);
// Switchmap classes should never be affected by renaming.
@@ -1731,6 +1745,8 @@
this.brokenSuperInvokes = previous.brokenSuperInvokes;
this.protoLiteFields = previous.protoLiteFields;
this.alwaysInline = previous.alwaysInline;
+ this.forceInline = previous.forceInline;
+ this.neverInline = previous.neverInline;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = previous.prunedTypes;
this.switchMaps = switchMaps;
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
new file mode 100644
index 0000000..565856c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2017, 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.shaking;
+
+import com.android.tools.r8.errors.Unreachable;
+import java.util.List;
+
+public class InlineRule extends ProguardConfigurationRule {
+
+ public enum Type {
+ ALWAYS, FORCE, NEVER
+ }
+
+ public static class Builder extends ProguardConfigurationRule.Builder {
+
+ private Builder() {
+ }
+
+ Type type;
+
+ public Builder setType(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ public InlineRule build() {
+ return new InlineRule(classAnnotation, classAccessFlags,
+ negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+ inheritanceClassName, inheritanceIsExtends, memberRules, type);
+ }
+ }
+
+ private final Type type;
+
+ private InlineRule(
+ ProguardTypeMatcher classAnnotation,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules,
+ Type type) {
+ super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+ classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ this.type = type;
+ }
+
+ public static InlineRule.Builder builder() {
+ return new InlineRule.Builder();
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public ProguardCheckDiscardRule asProguardCheckDiscardRule() {
+ assert type == Type.FORCE;
+ ProguardCheckDiscardRule.Builder builder = ProguardCheckDiscardRule.builder();
+ builder.setClassAnnotation(getClassAnnotation());
+ builder.setClassAccessFlags(getClassAccessFlags());
+ builder.setNegatedClassAccessFlags(getNegatedClassAccessFlags());
+ builder.setClassTypeNegated(getClassTypeNegated());
+ builder.setClassType(getClassType());
+ builder.setClassNames(getClassNames());
+ builder.setInheritanceAnnotation(getInheritanceAnnotation());
+ builder.setInheritanceIsExtends(getInheritanceIsExtends());
+ builder.setMemberRules(getMemberRules());
+ return builder.build();
+ }
+
+ @Override
+ String typeString() {
+ switch (type) {
+ case ALWAYS:
+ return "alwaysinline";
+ case FORCE:
+ return "forceinline";
+ case NEVER:
+ return "neverinline";
+ }
+ throw new Unreachable("Unknown inline type " + type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java
deleted file mode 100644
index 8d4a14b..0000000
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) 2017, 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.shaking;
-
-import java.util.List;
-
-public class ProguardAlwaysInlineRule extends ProguardConfigurationRule {
-
- public static class Builder extends ProguardConfigurationRule.Builder {
-
- private Builder() {
- }
-
- public ProguardAlwaysInlineRule build() {
- return new ProguardAlwaysInlineRule(classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules);
- }
- }
-
- private ProguardAlwaysInlineRule(
- ProguardTypeMatcher classAnnotation,
- ProguardAccessFlags classAccessFlags,
- ProguardAccessFlags negatedClassAccessFlags,
- boolean classTypeNegated,
- ProguardClassType classType,
- ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
- ProguardTypeMatcher inheritanceClassName,
- boolean inheritanceIsExtends,
- List<ProguardMemberRule> memberRules) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
- }
-
- public static ProguardAlwaysInlineRule.Builder builder() {
- return new ProguardAlwaysInlineRule.Builder();
- }
-
- @Override
- String typeString() {
- return "alwaysinline";
- }
-}
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 c4276dc..5b22456 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
+import com.android.tools.r8.Version;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -15,6 +16,7 @@
import com.android.tools.r8.position.Position;
import com.android.tools.r8.position.TextPosition;
import com.android.tools.r8.position.TextRange;
+import com.android.tools.r8.shaking.InlineRule.Type;
import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
@@ -48,6 +50,7 @@
private final Reporter reporter;
private final boolean failOnPartiallyImplementedOptions;
+ private final boolean allowTestOptions;
private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList.of(
"protomapping",
@@ -97,16 +100,18 @@
public ProguardConfigurationParser(
DexItemFactory dexItemFactory, Reporter reporter) {
- this(dexItemFactory, reporter, true);
+ this(dexItemFactory, reporter, true, false);
}
public ProguardConfigurationParser(
- DexItemFactory dexItemFactory, Reporter reporter, boolean failOnPartiallyImplementedOptions) {
+ DexItemFactory dexItemFactory, Reporter reporter, boolean failOnPartiallyImplementedOptions,
+ boolean allowTestOptions) {
this.dexItemFactory = dexItemFactory;
configurationBuilder = ProguardConfiguration.builder(dexItemFactory, reporter);
this.reporter = reporter;
this.failOnPartiallyImplementedOptions = failOnPartiallyImplementedOptions;
+ this.allowTestOptions = allowTestOptions;
}
public ProguardConfiguration.Builder getConfigurationBuilder() {
@@ -343,7 +348,16 @@
} else if (acceptString("packageobfuscationdictionary")) {
configurationBuilder.setPackageObfuscationDictionary(parseFileName());
} else if (acceptString("alwaysinline")) {
- ProguardAlwaysInlineRule rule = parseAlwaysInlineRule();
+ InlineRule rule = parseInlineRule(Type.ALWAYS);
+ configurationBuilder.addRule(rule);
+ } else if (allowTestOptions && acceptString("forceinline")) {
+ InlineRule rule = parseInlineRule(Type.FORCE);
+ configurationBuilder.addRule(rule);
+ // Insert a matching -checkdiscard rule to ensure force inlining happens.
+ ProguardCheckDiscardRule ruled = rule.asProguardCheckDiscardRule();
+ configurationBuilder.addRule(ruled);
+ } else if (allowTestOptions && acceptString("neverinline")) {
+ InlineRule rule = parseInlineRule(Type.NEVER);
configurationBuilder.addRule(rule);
} else if (acceptString("useuniqueclassmembernames")) {
configurationBuilder.setUseUniqueClassMemberNames(true);
@@ -367,8 +381,15 @@
configurationBuilder.addRule(parseIfRule(optionStart));
} else {
String unknownOption = acceptString();
+ String devMessage = "";
+ if (Version.isDev()
+ && unknownOption != null
+ && (unknownOption.equals("forceinline") || unknownOption.equals("neverinline"))) {
+ devMessage = ", this option needs to be turned on explicitly if used for tests.";
+ }
reporter.error(new StringDiagnostic(
- "Unknown option \"-" + unknownOption + "\"", origin, getPosition(optionStart)));
+ "Unknown option \"-" + unknownOption + "\"" + devMessage,
+ origin, getPosition(optionStart)));
}
return true;
}
@@ -563,9 +584,9 @@
return keepRuleBuilder.build();
}
- private ProguardAlwaysInlineRule parseAlwaysInlineRule()
+ private InlineRule parseInlineRule(InlineRule.Type type)
throws ProguardRuleParserException {
- ProguardAlwaysInlineRule.Builder keepRuleBuilder = ProguardAlwaysInlineRule.builder();
+ InlineRule.Builder keepRuleBuilder = InlineRule.builder().setType(type);
parseClassSpec(keepRuleBuilder, false);
return keepRuleBuilder.build();
}
@@ -873,6 +894,7 @@
ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
ruleBuilder.setArguments(parseArgumentList());
} else {
+ TextPosition firstStart = getPosition();
IdentifierPatternWithWildcards first =
acceptIdentifierWithBackreference(IdentifierType.ANY);
if (first != null) {
@@ -885,6 +907,7 @@
ruleBuilder.setName(first);
ruleBuilder.setArguments(parseArgumentList());
} else {
+ TextPosition secondStart = getPosition();
IdentifierPatternWithWildcards second =
acceptIdentifierWithBackreference(IdentifierType.ANY);
if (second != null) {
@@ -897,6 +920,12 @@
ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
ruleBuilder.setArguments(parseArgumentList());
} else {
+ if (first.hasUnusualCharacters()) {
+ warnUnusualCharacters("type", first.pattern, "field", firstStart);
+ }
+ if (second.hasUnusualCharacters()) {
+ warnUnusualCharacters("field name", second.pattern, "field", secondStart);
+ }
ruleBuilder.setRuleType(ProguardMemberType.FIELD);
ruleBuilder.setName(second);
ruleBuilder
@@ -1490,6 +1519,16 @@
"Option -" + optionName + " overrides -" + victim, origin, getPosition(start)));
}
+ private void warnUnusualCharacters(
+ String kind, String pattern, String ruleType, TextPosition start) {
+ reporter.warning(new StringDiagnostic(
+ "The " + kind + " \"" + pattern + "\" is used in a " + ruleType + " rule. The "
+ + "characters in this " + kind + " are legal for the JVM, "
+ + "but unlikely to originate from a source language. "
+ + "Maybe this is not the rule you are looking for.",
+ origin, getPosition(start)));
+ }
+
private void failPartiallyImplementedOption(String optionName, TextPosition start) {
throw reporter.fatalError(new StringDiagnostic(
"Option " + optionName + " currently not supported", origin, getPosition(start)));
@@ -1528,5 +1567,25 @@
boolean isMatchAllNames() {
return pattern.equals("*");
}
+
+ boolean hasUnusualCharacters() {
+ if (pattern.contains("<") || pattern.contains(">")) {
+ int angleStartCount = 0;
+ int angleEndCount = 0;
+ for (int i = 0; i < pattern.length(); i++) {
+ char c = pattern.charAt(i);
+ if (c == '<') {
+ angleStartCount++;
+ }
+ if (c == '>') {
+ angleEndCount++;
+ }
+ }
+ // Check that start/end angles are matched, and *only* used for well-formed wildcard
+ // backreferences (e.g. '<1>', but not '<<1>>', '<<*>>' or '>1<').
+ return !(angleStartCount == angleEndCount && angleStartCount == wildcards.size());
+ }
+ return false;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index ce1840f..e4c5691 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -56,7 +56,7 @@
if (!(o instanceof ProguardConfigurationRule)) {
return false;
}
- ProguardKeepRule that = (ProguardKeepRule) o;
+ ProguardConfigurationRule that = (ProguardConfigurationRule) o;
return super.equals(that);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index c860d83..c95cfe9 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -60,6 +60,8 @@
Sets.newIdentityHashSet();
private final Set<DexItem> checkDiscarded = Sets.newIdentityHashSet();
private final Set<DexItem> alwaysInline = Sets.newIdentityHashSet();
+ private final Set<DexItem> forceInline = Sets.newIdentityHashSet();
+ private final Set<DexItem> neverInline = Sets.newIdentityHashSet();
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
@@ -240,7 +242,7 @@
} else if (rule instanceof ProguardAssumeNoSideEffectRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
- } else if (rule instanceof ProguardAlwaysInlineRule) {
+ } else if (rule instanceof InlineRule) {
markMatchingMethods(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ProguardAssumeValuesRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
@@ -310,6 +312,8 @@
keepPackageName,
checkDiscarded,
alwaysInline,
+ forceInline,
+ neverInline,
noSideEffects,
assumedValues,
dependentNoShrinking,
@@ -720,8 +724,20 @@
assumedValues.put(item, rule);
} else if (context instanceof ProguardCheckDiscardRule) {
checkDiscarded.add(item);
- } else if (context instanceof ProguardAlwaysInlineRule) {
- alwaysInline.add(item);
+ } else if (context instanceof InlineRule) {
+ switch (((InlineRule) context).getType()) {
+ case ALWAYS:
+ alwaysInline.add(item);
+ break;
+ case FORCE:
+ forceInline.add(item);
+ break;
+ case NEVER:
+ neverInline.add(item);
+ break;
+ default:
+ throw new Unreachable();
+ }
} else if (context instanceof ProguardIdentifierNameStringRule) {
if (item instanceof DexEncodedField) {
identifierNameStrings.add(((DexEncodedField) item).field);
@@ -740,6 +756,8 @@
public final Set<DexItem> keepPackageName;
public final Set<DexItem> checkDiscarded;
public final Set<DexItem> alwaysInline;
+ public final Set<DexItem> forceInline;
+ public final Set<DexItem> neverInline;
public final Map<DexItem, ProguardMemberRule> noSideEffects;
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
@@ -775,6 +793,8 @@
Set<DexItem> keepPackageName,
Set<DexItem> checkDiscarded,
Set<DexItem> alwaysInline,
+ Set<DexItem> forceInline,
+ Set<DexItem> neverInline,
Map<DexItem, ProguardMemberRule> noSideEffects,
Map<DexItem, ProguardMemberRule> assumedValues,
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
@@ -787,6 +807,8 @@
this.keepPackageName = Collections.unmodifiableSet(keepPackageName);
this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
+ this.forceInline = Collections.unmodifiableSet(forceInline);
+ this.neverInline = Collections.unmodifiableSet(neverInline);
this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
this.assumedValues = Collections.unmodifiableMap(assumedValues);
this.dependentNoShrinking = dependentNoShrinking;
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 6f935ee..0840d91 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
@@ -21,12 +22,14 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.Builder;
+import com.android.tools.r8.graph.JarCode;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.logging.Log;
@@ -37,9 +40,9 @@
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterators;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayDeque;
@@ -55,9 +58,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.BiFunction;
import java.util.function.Predicate;
-import java.util.stream.Collectors;
/**
* Merges Supertypes with a single implementation into their single subtype.
@@ -74,6 +75,7 @@
ALWAYS_INLINE,
CONFLICT,
ILLEGAL_ACCESS,
+ NATIVE_METHOD,
NO_SIDE_EFFECTS,
PINNED_SOURCE,
RESOLUTION_FOR_FIELDS_MAY_CHANGE,
@@ -101,6 +103,9 @@
case ILLEGAL_ACCESS:
message = "it could lead to illegal accesses";
break;
+ case NATIVE_METHOD:
+ message = "it has a native method";
+ break;
case NO_SIDE_EFFECTS:
message = "it is mentioned in appInfo.noSideEffects";
break;
@@ -141,40 +146,61 @@
private final Timing timing;
private Collection<DexMethod> invokes;
+ // Set of merge candidates. Note that this must have a deterministic iteration order.
+ private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>();
+
// Map from source class to target class.
private final Map<DexType, DexType> mergedClasses = new HashMap<>();
// Map from target class to the super classes that have been merged into the target class.
private final Map<DexType, Set<DexType>> mergedClassesInverse = new HashMap<>();
+ // Set of types that must not be merged into their subtype.
+ private final Set<DexType> pinnedTypes = new HashSet<>();
+
// The resulting graph lense that should be used after class merging.
private final VerticalClassMergerGraphLense.Builder renamedMembersLense;
public VerticalClassMerger(
- DexApplication application,
- AppInfoWithLiveness appInfo,
- GraphLense graphLense,
- Timing timing) {
+ DexApplication application, AppView<AppInfoWithLiveness> appView, Timing timing) {
this.application = application;
- this.appInfo = appInfo;
- this.graphLense = graphLense;
+ this.appInfo = appView.getAppInfo();
+ this.graphLense = appView.getGraphLense();
this.renamedMembersLense = VerticalClassMergerGraphLense.builder(appInfo);
this.timing = timing;
+
+ Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
+ initializePinnedTypes(classes); // Must be initialized prior to mergeCandidates.
+ initializeMergeCandidates(classes);
+ }
+
+ private void initializeMergeCandidates(Iterable<DexProgramClass> classes) {
+ for (DexProgramClass clazz : classes) {
+ if (isMergeCandidate(clazz, pinnedTypes) && isStillMergeCandidate(clazz)) {
+ mergeCandidates.add(clazz);
+ }
+ }
}
// Returns a set of types that must not be merged into other types.
- private Set<DexType> getPinnedTypes(Iterable<DexProgramClass> classes) {
- Set<DexType> pinnedTypes = new HashSet<>();
-
+ private void initializePinnedTypes(Iterable<DexProgramClass> classes) {
// For all pinned fields, also pin the type of the field (because changing the type of the field
// implicitly changes the signature of the field). Similarly, for all pinned methods, also pin
// the return type and the parameter types of the method.
- extractPinnedItems(appInfo.pinnedItems, pinnedTypes, AbortReason.PINNED_SOURCE);
+ extractPinnedItems(appInfo.pinnedItems, AbortReason.PINNED_SOURCE);
// TODO(christofferqa): Remove the invariant that the graph lense should not modify any
// methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
- extractPinnedItems(appInfo.alwaysInline, pinnedTypes, AbortReason.ALWAYS_INLINE);
- extractPinnedItems(appInfo.noSideEffects.keySet(), pinnedTypes, AbortReason.NO_SIDE_EFFECTS);
+ extractPinnedItems(appInfo.alwaysInline, AbortReason.ALWAYS_INLINE);
+ extractPinnedItems(appInfo.noSideEffects.keySet(), AbortReason.NO_SIDE_EFFECTS);
+
+ for (DexProgramClass clazz : classes) {
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (method.accessFlags.isNative()) {
+ markTypeAsPinned(clazz.type, AbortReason.NATIVE_METHOD);
+ }
+ }
+ }
// Avoid merging two types if this could remove a NoSuchMethodError, as illustrated by the
// following example. (Alternatively, it would be possible to merge A and B and rewrite the
@@ -196,41 +222,41 @@
pinnedTypes.add(signature.holder);
}
}
- return pinnedTypes;
}
- private void extractPinnedItems(
- Iterable<DexItem> items, Set<DexType> pinnedTypes, AbortReason reason) {
+ private void extractPinnedItems(Iterable<DexItem> items, AbortReason reason) {
for (DexItem item : items) {
if (item instanceof DexType || item instanceof DexClass) {
DexType type = item instanceof DexType ? (DexType) item : ((DexClass) item).type;
- // We check for the case where the type is pinned according to appInfo.isPinned, so only
- // add it here if it is not the case.
- if (!appInfo.isPinned(type)) {
- markTypeAsPinned(type, pinnedTypes, reason);
- }
+ markTypeAsPinned(type, reason);
} else if (item instanceof DexField || item instanceof DexEncodedField) {
// Pin the holder and the type of the field.
DexField field =
item instanceof DexField ? (DexField) item : ((DexEncodedField) item).field;
- markTypeAsPinned(field.clazz, pinnedTypes, reason);
- markTypeAsPinned(field.type, pinnedTypes, reason);
+ markTypeAsPinned(field.clazz, reason);
+ markTypeAsPinned(field.type, reason);
} else if (item instanceof DexMethod || item instanceof DexEncodedMethod) {
// Pin the holder, the return type and the parameter types of the method. If we were to
// merge any of these types into their sub classes, then we would implicitly change the
// signature of this method.
DexMethod method =
item instanceof DexMethod ? (DexMethod) item : ((DexEncodedMethod) item).method;
- markTypeAsPinned(method.holder, pinnedTypes, reason);
- markTypeAsPinned(method.proto.returnType, pinnedTypes, reason);
+ markTypeAsPinned(method.holder, reason);
+ markTypeAsPinned(method.proto.returnType, reason);
for (DexType parameterType : method.proto.parameters.values) {
- markTypeAsPinned(parameterType, pinnedTypes, reason);
+ markTypeAsPinned(parameterType, reason);
}
}
}
}
- private void markTypeAsPinned(DexType type, Set<DexType> pinnedTypes, AbortReason reason) {
+ private void markTypeAsPinned(DexType type, AbortReason reason) {
+ if (appInfo.isPinned(type)) {
+ // We check for the case where the type is pinned according to appInfo.isPinned,
+ // so we only need to add it here if it is not the case.
+ return;
+ }
+
DexClass clazz = appInfo.definitionFor(type);
if (clazz != null && clazz.isProgramClass()) {
boolean changed = pinnedTypes.add(type);
@@ -243,6 +269,8 @@
}
}
+ // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
+ // method do not change in response to any class merges.
private boolean isMergeCandidate(DexProgramClass clazz, Set<DexType> pinnedTypes) {
if (appInfo.instantiatedTypes.contains(clazz.type)
|| appInfo.instantiatedLambdas.contains(clazz.type)
@@ -250,14 +278,8 @@
|| pinnedTypes.contains(clazz.type)) {
return false;
}
- if (mergedClassesInverse.containsKey(clazz.type)) {
- // Do not allow merging the resulting class into its subclass.
- // TODO(christofferqa): Get rid of this limitation.
- if (Log.ENABLED) {
- AbortReason.ALREADY_MERGED.printLogMessageForClass(clazz);
- }
- return false;
- }
+ // Note that the property "singleSubtype == null" cannot change during merging, since we visit
+ // classes in a top-down order.
DexType singleSubtype = clazz.type.getSingleSubtype();
if (singleSubtype == null) {
// TODO(christofferqa): Even if [clazz] has multiple subtypes, we could still merge it into
@@ -274,7 +296,7 @@
if (appInfo.isPinned(method.method)) {
return false;
}
- if (method.isInstanceInitializer() && disallowInlining(method)) {
+ if (method.isInstanceInitializer() && disallowInlining(method, singleSubtype)) {
// Cannot guarantee that markForceInline() will work.
if (Log.ENABLED) {
AbortReason.UNSAFE_INLINING.printLogMessageForClass(clazz);
@@ -282,13 +304,64 @@
return false;
}
}
- DexClass targetClass = appInfo.definitionFor(singleSubtype);
+ if (clazz.getEnclosingMethod() != null || !clazz.getInnerClasses().isEmpty()) {
+ // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+ if (Log.ENABLED) {
+ AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
+ // method may change in response to class merges. Therefore, this method should always be called
+ // before merging [clazz] into its subtype.
+ private boolean isStillMergeCandidate(DexProgramClass clazz) {
+ assert isMergeCandidate(clazz, pinnedTypes);
+ if (mergedClassesInverse.containsKey(clazz.type)) {
+ // Do not allow merging the resulting class into its subclass.
+ // TODO(christofferqa): Get rid of this limitation.
+ if (Log.ENABLED) {
+ AbortReason.ALREADY_MERGED.printLogMessageForClass(clazz);
+ }
+ return false;
+ }
+ DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
+ if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
+ // TODO(herhut): Handle class initializers.
+ if (Log.ENABLED) {
+ AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
+ }
+ return false;
+ }
+ if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
+ // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+ if (Log.ENABLED) {
+ AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
+ }
+ return false;
+ }
if (mergeMayLeadToIllegalAccesses(clazz, targetClass)) {
if (Log.ENABLED) {
AbortReason.ILLEGAL_ACCESS.printLogMessageForClass(clazz);
}
return false;
}
+ if (methodResolutionMayChange(clazz, targetClass)) {
+ if (Log.ENABLED) {
+ AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE.printLogMessageForClass(clazz);
+ }
+ return false;
+ }
+ // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
+ // to the super class.
+ if (fieldResolutionMayChange(clazz, targetClass)) {
+ if (Log.ENABLED) {
+ AbortReason.RESOLUTION_FOR_FIELDS_MAY_CHANGE.printLogMessageForClass(clazz);
+ }
+ return false;
+ }
return true;
}
@@ -337,49 +410,101 @@
return false;
}
- private void addProgramMethods(Set<Wrapper<DexMethod>> set, DexMethod method,
- Equivalence<DexMethod> equivalence) {
- DexClass definition = appInfo.definitionFor(method.holder);
- if (definition != null && definition.isProgramClass()) {
- set.add(equivalence.wrap(method));
- }
- }
-
private Collection<DexMethod> getInvokes() {
if (invokes == null) {
- // Collect all reachable methods that are not within a library class. Those defined on
- // library classes are known not to have program classes in their signature.
- // Also filter methods that only use types from library classes in their signatures. We
- // know that those won't conflict.
- Set<Wrapper<DexMethod>> filteredInvokes = new HashSet<>();
- Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
- appInfo.targetedMethods.forEach(m -> addProgramMethods(filteredInvokes, m, equivalence));
- invokes = filteredInvokes.stream().map(Wrapper::get).filter(this::removeNonProgram)
- .collect(Collectors.toList());
+ invokes = new OverloadedMethodSignaturesRetriever().get();
}
return invokes;
}
- private boolean isProgramClass(DexType type) {
- if (type.isArrayType()) {
- type = type.toBaseType(appInfo.dexItemFactory);
- }
- if (type.isClassType()) {
- DexClass clazz = appInfo.definitionFor(type);
- if (clazz != null && clazz.isProgramClass()) {
- return true;
- }
- }
- return false;
- }
+ // Collects all potentially overloaded method signatures that reference at least one type that
+ // may be the source or target of a merge operation.
+ private class OverloadedMethodSignaturesRetriever {
+ private final Reference2BooleanOpenHashMap<DexProto> cache =
+ new Reference2BooleanOpenHashMap<>();
+ private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+ private final Set<DexType> mergeeCandidates = new HashSet<>();
- private boolean removeNonProgram(DexMethod dexMethod) {
- for (DexType type : dexMethod.proto.parameters.values) {
- if (isProgramClass(type)) {
- return true;
+ public OverloadedMethodSignaturesRetriever() {
+ for (DexProgramClass mergeCandidate : mergeCandidates) {
+ mergeeCandidates.add(mergeCandidate.type.getSingleSubtype());
}
}
- return isProgramClass(dexMethod.proto.returnType);
+
+ public Collection<DexMethod> get() {
+ Map<DexString, DexProto> overloadingInfo = new HashMap<>();
+
+ // Find all signatures that may reference a type that could be the source or target of a
+ // merge operation.
+ Set<Wrapper<DexMethod>> filteredSignatures = new HashSet<>();
+ for (DexMethod signature : appInfo.targetedMethods) {
+ DexClass definition = appInfo.definitionFor(signature.holder);
+ if (definition != null
+ && definition.isProgramClass()
+ && protoMayReferenceMergedSourceOrTarget(signature.proto)) {
+ filteredSignatures.add(equivalence.wrap(signature));
+
+ // Record that we have seen a method named [signature.name] with the proto
+ // [signature.proto]. If at some point, we find a method with the same name, but a
+ // different proto, it could be the case that a method with the given name is overloaded.
+ DexProto existing =
+ overloadingInfo.computeIfAbsent(signature.name, key -> signature.proto);
+ if (existing != DexProto.SENTINEL && !existing.equals(signature.proto)) {
+ // Mark that this signature is overloaded by mapping it to SENTINEL.
+ overloadingInfo.put(signature.name, DexProto.SENTINEL);
+ }
+ }
+ }
+
+ List<DexMethod> result = new ArrayList<>();
+ for (Wrapper<DexMethod> wrappedSignature : filteredSignatures) {
+ DexMethod signature = wrappedSignature.get();
+
+ // Ignore those method names that are definitely not overloaded since they cannot lead to
+ // any collisions.
+ if (overloadingInfo.get(signature.name) == DexProto.SENTINEL) {
+ result.add(signature);
+ }
+ }
+ return result;
+ }
+
+ private boolean protoMayReferenceMergedSourceOrTarget(DexProto proto) {
+ boolean result;
+ if (cache.containsKey(proto)) {
+ result = cache.getBoolean(proto);
+ } else {
+ result = false;
+ if (typeMayReferenceMergedSourceOrTarget(proto.returnType)) {
+ result = true;
+ } else {
+ for (DexType type : proto.parameters.values) {
+ if (typeMayReferenceMergedSourceOrTarget(type)) {
+ result = true;
+ break;
+ }
+ }
+ }
+ cache.put(proto, result);
+ }
+ return result;
+ }
+
+ private boolean typeMayReferenceMergedSourceOrTarget(DexType type) {
+ if (type.isArrayType()) {
+ type = type.toBaseType(appInfo.dexItemFactory);
+ }
+ if (type.isClassType()) {
+ if (mergeeCandidates.contains(type)) {
+ return true;
+ }
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz != null && clazz.isProgramClass()) {
+ return mergeCandidates.contains(clazz.asProgramClass());
+ }
+ }
+ return false;
+ }
}
public GraphLense run() {
@@ -396,19 +521,21 @@
return result;
}
- private void addAncestorsToWorklist(
+ private void addCandidateAncestorsToWorklist(
DexProgramClass clazz, Deque<DexProgramClass> worklist, Set<DexProgramClass> seenBefore) {
if (seenBefore.contains(clazz)) {
return;
}
- worklist.addFirst(clazz);
+ if (mergeCandidates.contains(clazz)) {
+ worklist.addFirst(clazz);
+ }
// Add super classes to worklist.
if (clazz.superType != null) {
DexClass definition = appInfo.definitionFor(clazz.superType);
if (definition != null && definition.isProgramClass()) {
- addAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
+ addCandidateAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
}
}
@@ -416,71 +543,60 @@
for (DexType interfaceType : clazz.interfaces.values) {
DexClass definition = appInfo.definitionFor(interfaceType);
if (definition != null && definition.isProgramClass()) {
- addAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
+ addCandidateAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
}
}
}
private GraphLense mergeClasses(GraphLense graphLense) {
- Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
Deque<DexProgramClass> worklist = new ArrayDeque<>();
Set<DexProgramClass> seenBefore = new HashSet<>();
int numberOfMerges = 0;
- // Types that are pinned (in addition to those where appInfo.isPinned returns true).
- Set<DexType> pinnedTypes = getPinnedTypes(classes);
-
- Iterator<DexProgramClass> classIterator = classes.iterator();
+ Iterator<DexProgramClass> candidatesIterator = mergeCandidates.iterator();
// Visit the program classes in a top-down order according to the class hierarchy.
- while (classIterator.hasNext() || !worklist.isEmpty()) {
+ while (candidatesIterator.hasNext() || !worklist.isEmpty()) {
if (worklist.isEmpty()) {
// Add the ancestors of this class (including the class itself) to the worklist in such a
// way that all super types of the class come before the class itself.
- addAncestorsToWorklist(classIterator.next(), worklist, seenBefore);
+ addCandidateAncestorsToWorklist(candidatesIterator.next(), worklist, seenBefore);
if (worklist.isEmpty()) {
continue;
}
}
DexProgramClass clazz = worklist.removeFirst();
- if (!seenBefore.add(clazz) || !isMergeCandidate(clazz, pinnedTypes)) {
+ assert isMergeCandidate(clazz, pinnedTypes);
+ if (!seenBefore.add(clazz)) {
continue;
}
- DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
+ DexProgramClass targetClass =
+ appInfo.definitionFor(clazz.type.getSingleSubtype()).asProgramClass();
assert !mergedClasses.containsKey(targetClass.type);
- if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
- // TODO(herhut): Handle class initializers.
- if (Log.ENABLED) {
- AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
+
+ boolean clazzOrTargetClassHasBeenMerged =
+ mergedClassesInverse.containsKey(clazz.type)
+ || mergedClassesInverse.containsKey(targetClass.type);
+ if (clazzOrTargetClassHasBeenMerged) {
+ if (!isStillMergeCandidate(clazz)) {
+ continue;
}
- continue;
+ } else {
+ assert isStillMergeCandidate(clazz);
}
- if (methodResolutionMayChange(clazz, targetClass)) {
- if (Log.ENABLED) {
- AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE.printLogMessageForClass(clazz);
- }
- continue;
- }
- // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
- // to the super class.
- if (fieldResolutionMayChange(clazz, targetClass)) {
- if (Log.ENABLED) {
- AbortReason.RESOLUTION_FOR_FIELDS_MAY_CHANGE.printLogMessageForClass(clazz);
- }
- continue;
- }
+
// Guard against the case where we have two methods that may get the same signature
// if we replace types. This is rare, so we approximate and err on the safe side here.
- if (new CollisionDetector(clazz.type, targetClass.type, getInvokes(), mergedClasses)
- .mayCollide()) {
+ if (new CollisionDetector(clazz.type, targetClass.type).mayCollide()) {
if (Log.ENABLED) {
AbortReason.CONFLICT.printLogMessageForClass(clazz);
}
continue;
}
+
ClassMerger merger = new ClassMerger(clazz, targetClass);
boolean merged = merger.merge();
if (merged) {
@@ -598,14 +714,6 @@
}
public boolean merge() {
- if (source.getEnclosingMethod() != null || !source.getInnerClasses().isEmpty()
- || target.getEnclosingMethod() != null || !target.getInnerClasses().isEmpty()) {
- // TODO(herhut): Consider supporting merging of inner-class attributes.
- if (Log.ENABLED) {
- AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(source);
- }
- return false;
- }
// Merge the class [clazz] into [targetClass] by adding all methods to
// targetClass that are not currently contained.
// Step 1: Merge methods
@@ -697,34 +805,46 @@
deferredRenamings.map(virtualMethod.method, shadowedBy.method);
}
- Collection<DexEncodedMethod> mergedDirectMethods =
- mergeItems(
- directMethods.values().iterator(),
- target.directMethods(),
- MethodSignatureEquivalence.get(),
- this::resolveMethodConflict);
- Collection<DexEncodedMethod> mergedVirtualMethods =
- mergeItems(
- virtualMethods.values().iterator(),
- target.virtualMethods(),
- MethodSignatureEquivalence.get(),
- this::resolveMethodConflict);
if (abortMerge) {
return false;
}
+
+ DexEncodedMethod[] mergedDirectMethods =
+ mergeMethods(directMethods.values(), target.directMethods());
+ DexEncodedMethod[] mergedVirtualMethods =
+ mergeMethods(virtualMethods.values(), target.virtualMethods());
+
// Step 2: Merge fields
- Collection<DexEncodedField> mergedStaticFields =
- mergeItems(
- Iterators.forArray(source.staticFields()),
- target.staticFields(),
- FieldSignatureEquivalence.get(),
- this::resolveFieldConflict);
- Collection<DexEncodedField> mergedInstanceFields =
- mergeItems(
- Iterators.forArray(source.instanceFields()),
+ Set<DexString> existingFieldNames = new HashSet<>();
+ for (DexEncodedField field : target.fields()) {
+ existingFieldNames.add(field.field.name);
+ }
+
+ // In principle, we could allow multiple fields with the same name, and then only rename the
+ // field in the end when we are done merging all the classes, if it it turns out that the two
+ // fields ended up having the same type. This would not be too expensive, since we visit the
+ // entire program using VerticalClassMerger.TreeFixer anyway.
+ //
+ // For now, we conservatively report that a signature is already taken if there is a field
+ // with the same name. If minification is used with -overloadaggressively, this is solved
+ // later anyway.
+ Predicate<DexField> availableFieldSignatures =
+ field -> !existingFieldNames.contains(field.name);
+
+ DexEncodedField[] mergedInstanceFields =
+ mergeFields(
+ source.instanceFields(),
target.instanceFields(),
- FieldSignatureEquivalence.get(),
- this::resolveFieldConflict);
+ availableFieldSignatures,
+ existingFieldNames);
+
+ DexEncodedField[] mergedStaticFields =
+ mergeFields(
+ source.staticFields(),
+ target.staticFields(),
+ availableFieldSignatures,
+ existingFieldNames);
+
// Step 3: Merge interfaces
Set<DexType> interfaces = mergeArrays(target.interfaces.values, source.interfaces.values);
// Now destructively update the class.
@@ -739,14 +859,10 @@
? DexTypeList.empty()
: new DexTypeList(interfaces.toArray(new DexType[interfaces.size()]));
// Step 2: replace fields and methods.
- target.setDirectMethods(mergedDirectMethods
- .toArray(new DexEncodedMethod[mergedDirectMethods.size()]));
- target.setVirtualMethods(mergedVirtualMethods
- .toArray(new DexEncodedMethod[mergedVirtualMethods.size()]));
- target.setStaticFields(mergedStaticFields
- .toArray(new DexEncodedField[mergedStaticFields.size()]));
- target.setInstanceFields(mergedInstanceFields
- .toArray(new DexEncodedField[mergedInstanceFields.size()]));
+ target.setDirectMethods(mergedDirectMethods);
+ target.setVirtualMethods(mergedVirtualMethods);
+ target.setInstanceFields(mergedInstanceFields);
+ target.setStaticFields(mergedStaticFields);
// Step 3: Unlink old class to ease tree shaking.
source.superType = application.dexItemFactory.objectType;
source.setDirectMethods(null);
@@ -834,15 +950,15 @@
}
private DexEncodedMethod buildBridgeMethod(
- DexEncodedMethod signature, DexMethod invocationTarget) {
+ DexEncodedMethod method, DexMethod invocationTarget) {
DexType holder = target.type;
DexProto proto = invocationTarget.proto;
- DexString name = signature.method.name;
- MethodAccessFlags accessFlags = signature.accessFlags.copy();
+ DexString name = method.method.name;
+ MethodAccessFlags accessFlags = method.accessFlags.copy();
accessFlags.setBridge();
accessFlags.setSynthetic();
accessFlags.unsetAbstract();
- return new DexEncodedMethod(
+ DexEncodedMethod bridge = new DexEncodedMethod(
application.dexItemFactory.createMethod(holder, proto, name),
accessFlags,
DexAnnotationSet.empty(),
@@ -850,7 +966,14 @@
new SynthesizedCode(
new ForwardMethodSourceCode(holder, proto, holder, invocationTarget, Type.DIRECT),
registry -> registry.registerInvokeDirect(invocationTarget)),
- signature.hasClassFileVersion() ? signature.getClassFileVersion() : -1);
+ method.hasClassFileVersion() ? method.getClassFileVersion() : -1);
+ if (method.getOptimizationInfo().isPublicized()) {
+ // The bridge is now the public method serving the role of the original method, and should
+ // reflect that this method was publicized.
+ bridge.markPublicized();
+ method.unsetPublicized();
+ }
+ return bridge;
}
// Returns the method that shadows the given method, or null if method is not shadowed.
@@ -889,41 +1012,38 @@
return merged;
}
- private <T extends PresortedComparable<T>, S extends KeyedDexItem<T>> Collection<S> mergeItems(
- Iterator<S> fromItems,
- S[] toItems,
- Equivalence<T> equivalence,
- BiFunction<S, Predicate<T>, S> onConflict) {
- Map<Wrapper<T>, S> map = new HashMap<>();
- // First add everything from the target class. These items are not preprocessed.
- for (S item : toItems) {
- map.put(equivalence.wrap(item.getKey()), item);
+ private DexEncodedField[] mergeFields(
+ DexEncodedField[] sourceFields,
+ DexEncodedField[] targetFields,
+ Predicate<DexField> availableFieldSignatures,
+ Set<DexString> existingFieldNames) {
+ DexEncodedField[] result = new DexEncodedField[sourceFields.length + targetFields.length];
+ // Add fields from source
+ int i = 0;
+ for (DexEncodedField field : sourceFields) {
+ DexEncodedField resultingField = renameFieldIfNeeded(field, availableFieldSignatures);
+ existingFieldNames.add(resultingField.field.name);
+ deferredRenamings.map(field.field, resultingField.field);
+ result[i] = resultingField;
+ i++;
}
- // Now add the new items, resolving shadowing.
- addNonShadowed(fromItems, map, equivalence, onConflict);
- return map.values();
+ // Add fields from target.
+ System.arraycopy(targetFields, 0, result, i, targetFields.length);
+ return result;
}
- private <T extends PresortedComparable<T>, S extends KeyedDexItem<T>> void addNonShadowed(
- Iterator<S> items,
- Map<Wrapper<T>, S> map,
- Equivalence<T> equivalence,
- BiFunction<S, Predicate<T>, S> onConflict) {
- Predicate<T> availableSignatures = key -> !map.containsKey(equivalence.wrap(key));
- while (items.hasNext()) {
- S item = items.next();
- assert item != null;
- Wrapper<T> wrapped = equivalence.wrap(item.getKey());
- if (map.containsKey(wrapped)) {
- S resolved = onConflict.apply(item, availableSignatures);
- assert availableSignatures.test(resolved.getKey());
- wrapped = equivalence.wrap(resolved.getKey());
- map.put(wrapped, resolved);
- assert !availableSignatures.test(resolved.getKey());
- } else {
- map.put(wrapped, item);
- }
+ private DexEncodedMethod[] mergeMethods(
+ Collection<DexEncodedMethod> sourceMethods, DexEncodedMethod[] targetMethods) {
+ DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.size() + targetMethods.length];
+ // Add methods from source.
+ int i = 0;
+ for (DexEncodedMethod method : sourceMethods) {
+ result[i] = method;
+ i++;
}
+ // Add methods from target.
+ System.arraycopy(targetMethods, 0, result, i, targetMethods.length);
+ return result;
}
// Note that names returned by this function are not necessarily unique. Clients should
@@ -936,13 +1056,6 @@
return application.dexItemFactory.createString(freshName);
}
- private DexEncodedMethod resolveMethodConflict(
- DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures) {
- assert false;
- abortMerge = true;
- return method;
- }
-
private DexEncodedMethod renameConstructor(
DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures) {
assert method.isInstanceInitializer();
@@ -1008,23 +1121,24 @@
return method.toTypeSubstitutedMethod(newSignature);
}
- private DexEncodedField resolveFieldConflict(
+ private DexEncodedField renameFieldIfNeeded(
DexEncodedField field, Predicate<DexField> availableFieldSignatures) {
DexString oldName = field.field.name;
DexType oldHolder = field.field.clazz;
- DexField newSignature;
- int count = 1;
- do {
- DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
- newSignature =
- application.dexItemFactory.createField(target.type, field.field.type, newName);
- count++;
- } while (!availableFieldSignatures.test(newSignature));
+ DexField newSignature =
+ application.dexItemFactory.createField(target.type, field.field.type, oldName);
+ if (!availableFieldSignatures.test(newSignature)) {
+ int count = 1;
+ do {
+ DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
+ newSignature =
+ application.dexItemFactory.createField(target.type, field.field.type, newName);
+ count++;
+ } while (!availableFieldSignatures.test(newSignature));
+ }
- DexEncodedField result = field.toTypeSubstitutedField(newSignature);
- deferredRenamings.map(field.field, result.field);
- return result;
+ return field.toTypeSubstitutedField(newSignature);
}
}
@@ -1156,22 +1270,18 @@
private class CollisionDetector {
- private static final int NOT_FOUND = 1 << (Integer.SIZE - 1);
+ private static final int NOT_FOUND = Integer.MIN_VALUE;
// TODO(herhut): Maybe cache seenPositions for target classes.
private final Map<DexString, Int2IntMap> seenPositions = new IdentityHashMap<>();
private final Reference2IntMap<DexProto> targetProtoCache;
private final Reference2IntMap<DexProto> sourceProtoCache;
private final DexType source, target;
- private final Collection<DexMethod> invokes;
- private final Map<DexType, DexType> substituions;
+ private final Collection<DexMethod> invokes = getInvokes();
- private CollisionDetector(DexType source, DexType target, Collection<DexMethod> invokes,
- Map<DexType, DexType> substitutions) {
+ private CollisionDetector(DexType source, DexType target) {
this.source = source;
this.target = target;
- this.invokes = invokes;
- this.substituions = substitutions;
this.targetProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
this.targetProtoCache.defaultReturnValue(NOT_FOUND);
this.sourceProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
@@ -1180,7 +1290,7 @@
boolean mayCollide() {
timing.begin("collision detection");
- fillSeenPositions(invokes);
+ fillSeenPositions();
boolean result = false;
// If the type is not used in methods at all, there cannot be any conflict.
if (!seenPositions.isEmpty()) {
@@ -1191,8 +1301,7 @@
int previous = positionsMap.get(arity);
if (previous != NOT_FOUND) {
assert previous != 0;
- int positions =
- computePositionsFor(method.proto, source, sourceProtoCache, substituions);
+ int positions = computePositionsFor(method.proto, source, sourceProtoCache);
if ((positions & previous) != 0) {
result = true;
break;
@@ -1205,11 +1314,11 @@
return result;
}
- private void fillSeenPositions(Collection<DexMethod> invokes) {
+ private void fillSeenPositions() {
for (DexMethod method : invokes) {
DexType[] parameters = method.proto.parameters.values;
int arity = parameters.length;
- int positions = computePositionsFor(method.proto, target, targetProtoCache, substituions);
+ int positions = computePositionsFor(method.proto, target, targetProtoCache);
if (positions != 0) {
Int2IntMap positionsMap =
seenPositions.computeIfAbsent(method.name, k -> {
@@ -1229,8 +1338,10 @@
}
- private int computePositionsFor(DexProto proto, DexType type,
- Reference2IntMap<DexProto> cache, Map<DexType, DexType> substitutions) {
+ // Given a method signature and a type, this method computes a bit vector that denotes the
+ // positions at which the given type is used in the method signature.
+ private int computePositionsFor(
+ DexProto proto, DexType type, Reference2IntMap<DexProto> cache) {
int result = cache.getInt(proto);
if (result != NOT_FOUND) {
return result;
@@ -1239,13 +1350,8 @@
int bitsUsed = 0;
int accumulator = 0;
for (DexType aType : proto.parameters.values) {
- if (substitutions != null) {
- // Substitute the type with the already merged class to estimate what it will
- // look like.
- while (substitutions.containsKey(aType)) {
- aType = substitutions.get(aType);
- }
- }
+ // Substitute the type with the already merged class to estimate what it will look like.
+ aType = mergedClasses.getOrDefault(aType, aType);
accumulator <<= 1;
bitsUsed++;
if (aType == type) {
@@ -1259,12 +1365,7 @@
}
}
// We also take the return type into account for potential conflicts.
- DexType returnType = proto.returnType;
- if (substitutions != null) {
- while (substitutions.containsKey(returnType)) {
- returnType = substitutions.get(returnType);
- }
- }
+ DexType returnType = mergedClasses.getOrDefault(proto.returnType, proto.returnType);
accumulator <<= 1;
if (returnType == type) {
accumulator |= 1;
@@ -1275,84 +1376,73 @@
}
}
- private static boolean disallowInlining(DexEncodedMethod method) {
+ private boolean disallowInlining(DexEncodedMethod method, DexType invocationContext) {
// TODO(christofferqa): Determine the situations where markForceInline() may fail, and ensure
// that we always return true here in these cases.
- MethodInlineDecision registry = new MethodInlineDecision();
- method.getCode().registerCodeReferences(registry);
- return registry.isInliningDisallowed();
+ if (method.getCode().isJarCode()) {
+ JarCode jarCode = method.getCode().asJarCode();
+ Constraint constraint =
+ jarCode.computeInliningConstraint(
+ method,
+ appInfo,
+ new SingleTypeMapperGraphLense(method.method.holder, invocationContext),
+ invocationContext);
+ return constraint == Constraint.NEVER;
+ }
+ // TODO(christofferqa): For non-jar code we currently cannot guarantee that markForceInline()
+ // will succeed.
+ return true;
}
- private static class MethodInlineDecision extends UseRegistry {
- private boolean disallowInlining = false;
+ private class SingleTypeMapperGraphLense extends GraphLense {
- public boolean isInliningDisallowed() {
- return disallowInlining;
+ private final DexType source;
+ private final DexType target;
+
+ public SingleTypeMapperGraphLense(DexType source, DexType target) {
+ this.source = source;
+ this.target = target;
}
- private boolean allowInlining() {
+ @Override
+ public DexType lookupType(DexType type) {
+ return type == source ? target : mergedClasses.getOrDefault(type, type);
+ }
+
+ @Override
+ public GraphLenseLookupResult lookupMethod(
+ DexMethod method, DexEncodedMethod context, Type type) {
+ // First look up the method using the existing graph lense (for example, the type will have
+ // changed if the method was publicized by ClassAndMemberPublicizer).
+ GraphLenseLookupResult lookup = graphLense.lookupMethod(method, context, type);
+ DexMethod previousMethod = lookup.getMethod();
+ Type previousType = lookup.getType();
+ // Then check if there is a renaming due to the vertical class merger.
+ DexMethod newMethod = renamedMembersLense.methodMap.get(previousMethod);
+ if (newMethod != null) {
+ if (previousType == Type.INTERFACE) {
+ // If an interface has been merged into a class, invoke-interface needs to be translated
+ // to invoke-virtual.
+ DexClass clazz = appInfo.definitionFor(newMethod.holder);
+ if (clazz != null && !clazz.accessFlags.isInterface()) {
+ assert appInfo.definitionFor(method.holder).accessFlags.isInterface();
+ return new GraphLenseLookupResult(newMethod, Type.VIRTUAL);
+ }
+ }
+ return new GraphLenseLookupResult(newMethod, previousType);
+ }
+ return new GraphLenseLookupResult(previousMethod, previousType);
+ }
+
+ @Override
+ public DexField lookupField(DexField field) {
+ return renamedMembersLense.fieldMap.getOrDefault(field, field);
+ }
+
+ @Override
+ public boolean isContextFreeForMethods() {
return true;
}
-
- private boolean disallowInlining() {
- disallowInlining = true;
- return true;
- }
-
- @Override
- public boolean registerInvokeInterface(DexMethod method) {
- return disallowInlining();
- }
-
- @Override
- public boolean registerInvokeVirtual(DexMethod method) {
- return disallowInlining();
- }
-
- @Override
- public boolean registerInvokeDirect(DexMethod method) {
- return allowInlining();
- }
-
- @Override
- public boolean registerInvokeStatic(DexMethod method) {
- return allowInlining();
- }
-
- @Override
- public boolean registerInvokeSuper(DexMethod method) {
- return allowInlining();
- }
-
- @Override
- public boolean registerInstanceFieldWrite(DexField field) {
- return allowInlining();
- }
-
- @Override
- public boolean registerInstanceFieldRead(DexField field) {
- return allowInlining();
- }
-
- @Override
- public boolean registerNewInstance(DexType type) {
- return allowInlining();
- }
-
- @Override
- public boolean registerStaticFieldRead(DexField field) {
- return allowInlining();
- }
-
- @Override
- public boolean registerStaticFieldWrite(DexField field) {
- return allowInlining();
- }
-
- @Override
- public boolean registerTypeReference(DexType type) {
- return allowInlining();
- }
}
// Searches for a reference to a non-public class, field or method declared in the same package
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 4f614b4..fa10215 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -136,9 +136,8 @@
public static class Builder {
private final AppInfo appInfo;
- private final ImmutableMap.Builder<DexField, DexField> fieldMapBuilder = ImmutableMap.builder();
- private final ImmutableMap.Builder<DexMethod, DexMethod> methodMapBuilder =
- ImmutableMap.builder();
+ protected final Map<DexField, DexField> fieldMap = new HashMap<>();
+ protected final Map<DexMethod, DexMethod> methodMap = new HashMap<>();
private final ImmutableSet.Builder<DexMethod> mergedMethodsBuilder = ImmutableSet.builder();
private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps =
new HashMap<>();
@@ -151,8 +150,6 @@
GraphLense previousLense,
Map<DexType, DexType> mergedClasses,
DexItemFactory dexItemFactory) {
- Map<DexField, DexField> fieldMap = fieldMapBuilder.build();
- Map<DexMethod, DexMethod> methodMap = methodMapBuilder.build();
if (fieldMap.isEmpty()
&& methodMap.isEmpty()
&& contextualVirtualToDirectMethodMaps.isEmpty()) {
@@ -205,11 +202,11 @@
}
public void map(DexField from, DexField to) {
- fieldMapBuilder.put(from, to);
+ fieldMap.put(from, to);
}
public void map(DexMethod from, DexMethod to) {
- methodMapBuilder.put(from, to);
+ methodMap.put(from, to);
}
public void mapVirtualMethodToDirectInType(DexMethod from, DexMethod to, DexType type) {
@@ -219,8 +216,8 @@
}
public void merge(VerticalClassMergerGraphLense.Builder builder) {
- fieldMapBuilder.putAll(builder.fieldMapBuilder.build());
- methodMapBuilder.putAll(builder.methodMapBuilder.build());
+ fieldMap.putAll(builder.fieldMap);
+ methodMap.putAll(builder.methodMap);
mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
Map<DexMethod, DexMethod> current = contextualVirtualToDirectMethodMaps.get(context);
diff --git a/src/test/examples/classmerging/ClassWithNativeMethodTest.java b/src/test/examples/classmerging/ClassWithNativeMethodTest.java
new file mode 100644
index 0000000..b28433d
--- /dev/null
+++ b/src/test/examples/classmerging/ClassWithNativeMethodTest.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package classmerging;
+
+public class ClassWithNativeMethodTest {
+
+ public static void main(String[] args) {
+ B obj = new B();
+
+ // Make sure that A.method is not removed by tree shaking.
+ if (args.length == 42) {
+ obj.method();
+ }
+ }
+
+ public static class A {
+ public native void method();
+ }
+
+ public static class B extends A {}
+}
diff --git a/src/test/examples/classmerging/FieldCollisionTest.java b/src/test/examples/classmerging/FieldCollisionTest.java
new file mode 100644
index 0000000..5bb2260
--- /dev/null
+++ b/src/test/examples/classmerging/FieldCollisionTest.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class FieldCollisionTest {
+
+ private static final B SENTINEL_A = new B("A");
+ private static final B SENTINEL_B = new B("B");
+
+ public static void main(String[] args) {
+ B obj = new B();
+ System.out.println(obj.toString());
+ }
+
+ // Will be merged into B.
+ public static class A {
+
+ // After class merging, this field will have the same name and type as the field B.obj,
+ // unless we handle the collision.
+ protected final A obj = SENTINEL_A;
+ }
+
+ public static class B extends A {
+
+ protected final String message;
+ protected final B obj = SENTINEL_B;
+
+ public B() {
+ this(null);
+ }
+
+ public B(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return obj.message + System.lineSeparator() + ((B) super.obj).message;
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/MethodCollisionTest.java b/src/test/examples/classmerging/MethodCollisionTest.java
new file mode 100644
index 0000000..a9010a4
--- /dev/null
+++ b/src/test/examples/classmerging/MethodCollisionTest.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class MethodCollisionTest {
+
+ public static void main(String[] args) {
+ new B().m();
+ new D().m();
+ }
+
+ public static class A {
+
+ // After class merging, this method will have the same signature as the method B.m,
+ // unless we handle the collision.
+ private A m() {
+ System.out.println("A.m");
+ return null;
+ }
+
+ public void invokeM() {
+ m();
+ }
+ }
+
+ public static class B extends A {
+
+ private B m() {
+ System.out.println("B.m");
+ invokeM();
+ return null;
+ }
+ }
+
+ public static class C {
+
+ // After class merging, this method will have the same signature as the method D.m,
+ // unless we handle the collision.
+ public C m() {
+ System.out.println("C.m");
+ return null;
+ }
+ }
+
+ public static class D extends C {
+
+ public D m() {
+ System.out.println("D.m");
+ super.m();
+ return null;
+ }
+ }
+
+
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 516e39d..4ba014e 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
-keep public class classmerging.Test {
public static void main(...);
}
+-keep public class classmerging.ClassWithNativeMethodTest {
+ public static void main(...);
+}
-keep public class classmerging.ConflictInGeneratedNameTest {
public static void main(...);
}
@@ -16,6 +19,12 @@
-keep public class classmerging.ExceptionTest {
public static void main(...);
}
+-keep public class classmerging.FieldCollisionTest {
+ public static void main(...);
+}
+-keep public class classmerging.MethodCollisionTest {
+ public static void main(...);
+}
-keep public class classmerging.RewritePinnedMethodTest {
public static void main(...);
}
diff --git a/src/test/examples/regress_110373181/Regress.java b/src/test/examples/regress_110373181/Regress.java
new file mode 100644
index 0000000..ae0d5bc
--- /dev/null
+++ b/src/test/examples/regress_110373181/Regress.java
@@ -0,0 +1,1234 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package regress_110373181;
+
+public class Regress {
+ public static class Inner {
+ private int i0 = 0;
+ private int i1 = 1;
+ private int i2 = 2;
+ private int i3 = 3;
+ private int i4 = 4;
+ private int i5 = 5;
+ private int i6 = 6;
+ private int i7 = 7;
+ private int i8 = 8;
+ private int i9 = 9;
+ private int i10 = 10;
+ private int i11 = 11;
+ private int i12 = 12;
+ private int i13 = 13;
+ private int i14 = 14;
+ private int i15 = 15;
+ private int i16 = 16;
+ private int i17 = 17;
+ private int i18 = 18;
+ private int i19 = 19;
+ private int i20 = 20;
+ private int i21 = 21;
+ private int i22 = 22;
+ private int i23 = 23;
+ private int i24 = 24;
+ private int i25 = 25;
+ private int i26 = 26;
+ private int i27 = 27;
+ private int i28 = 28;
+ private int i29 = 29;
+ private int i30 = 30;
+ private int i31 = 31;
+ private int i32 = 32;
+ private int i33 = 33;
+ private int i34 = 34;
+ private int i35 = 35;
+ private int i36 = 36;
+ private int i37 = 37;
+ private int i38 = 38;
+ private int i39 = 39;
+ private int i40 = 40;
+ private int i41 = 41;
+ private int i42 = 42;
+ private int i43 = 43;
+ private int i44 = 44;
+ private int i45 = 45;
+ private int i46 = 46;
+ private int i47 = 47;
+ private int i48 = 48;
+ private int i49 = 49;
+ private int i50 = 50;
+ private int i51 = 51;
+ private int i52 = 52;
+ private int i53 = 53;
+ private int i54 = 54;
+ private int i55 = 55;
+ private int i56 = 56;
+ private int i57 = 57;
+ private int i58 = 58;
+ private int i59 = 59;
+ private int i60 = 60;
+ private int i61 = 61;
+ private int i62 = 62;
+ private int i63 = 63;
+ private int i64 = 64;
+ private int i65 = 65;
+ private int i66 = 66;
+ private int i67 = 67;
+ private int i68 = 68;
+ private int i69 = 69;
+ private int i70 = 70;
+ private int i71 = 71;
+ private int i72 = 72;
+ private int i73 = 73;
+ private int i74 = 74;
+ private int i75 = 75;
+ private int i76 = 76;
+ private int i77 = 77;
+ private int i78 = 78;
+ private int i79 = 79;
+ private int i80 = 80;
+ private int i81 = 81;
+ private int i82 = 82;
+ private int i83 = 83;
+ private int i84 = 84;
+ private int i85 = 85;
+ private int i86 = 86;
+ private int i87 = 87;
+ private int i88 = 88;
+ private int i89 = 89;
+ private int i90 = 90;
+ private int i91 = 91;
+ private int i92 = 92;
+ private int i93 = 93;
+ private int i94 = 94;
+ private int i95 = 95;
+ private int i96 = 96;
+ private int i97 = 97;
+ private int i98 = 98;
+ private int i99 = 99;
+ private int i100 = 100;
+ private int i101 = 101;
+ private int i102 = 102;
+ private int i103 = 103;
+ private int i104 = 104;
+ private int i105 = 105;
+ private int i106 = 106;
+ private int i107 = 107;
+ private int i108 = 108;
+ private int i109 = 109;
+ private int i110 = 110;
+ private int i111 = 111;
+ private int i112 = 112;
+ private int i113 = 113;
+ private int i114 = 114;
+ private int i115 = 115;
+ private int i116 = 116;
+ private int i117 = 117;
+ private int i118 = 118;
+ private int i119 = 119;
+ private int i120 = 120;
+ private int i121 = 121;
+ private int i122 = 122;
+ private int i123 = 123;
+ private int i124 = 124;
+ private int i125 = 125;
+ private int i126 = 126;
+ private int i127 = 127;
+ private int i128 = 128;
+ private int i129 = 129;
+ private int i130 = 130;
+ private int i131 = 131;
+ private int i132 = 132;
+ private int i133 = 133;
+ private int i134 = 134;
+ private int i135 = 135;
+ private int i136 = 136;
+ private int i137 = 137;
+ private int i138 = 138;
+ private int i139 = 139;
+ private int i140 = 140;
+ private int i141 = 141;
+ private int i142 = 142;
+ private int i143 = 143;
+ private int i144 = 144;
+ private int i145 = 145;
+ private int i146 = 146;
+ private int i147 = 147;
+ private int i148 = 148;
+ private int i149 = 149;
+ private int i150 = 150;
+ private int i151 = 151;
+ private int i152 = 152;
+ private int i153 = 153;
+ private int i154 = 154;
+ private int i155 = 155;
+ private int i156 = 156;
+ private int i157 = 157;
+ private int i158 = 158;
+ private int i159 = 159;
+ private int i160 = 160;
+ private int i161 = 161;
+ private int i162 = 162;
+ private int i163 = 163;
+ private int i164 = 164;
+ private int i165 = 165;
+ private int i166 = 166;
+ private int i167 = 167;
+ private int i168 = 168;
+ private int i169 = 169;
+ private int i170 = 170;
+ private int i171 = 171;
+ private int i172 = 172;
+ private int i173 = 173;
+ private int i174 = 174;
+ private int i175 = 175;
+ private int i176 = 176;
+ private int i177 = 177;
+ private int i178 = 178;
+ private int i179 = 179;
+ private int i180 = 180;
+ private int i181 = 181;
+ private int i182 = 182;
+ private int i183 = 183;
+ private int i184 = 184;
+ private int i185 = 185;
+ private int i186 = 186;
+ private int i187 = 187;
+ private int i188 = 188;
+ private int i189 = 189;
+ private int i190 = 190;
+ private int i191 = 191;
+ private int i192 = 192;
+ private int i193 = 193;
+ private int i194 = 194;
+ private int i195 = 195;
+ private int i196 = 196;
+ private int i197 = 197;
+ private int i198 = 198;
+ private int i199 = 199;
+
+ private String s0 = "s0";
+ private String s1 = "s1";
+ private String s2 = "s2";
+ private String s3 = "s3";
+ private String s4 = "s4";
+ private String s5 = "s5";
+ private String s6 = "s6";
+ private String s7 = "s7";
+ private String s8 = "s8";
+ private String s9 = "s9";
+ private String s10 = "s10";
+ private String s11 = "s11";
+ private String s12 = "s12";
+ private String s13 = "s13";
+ private String s14 = "s14";
+ private String s15 = "s15";
+ private String s16 = "s16";
+ private String s17 = "s17";
+ private String s18 = "s18";
+ private String s19 = "s19";
+ private String s20 = "s20";
+ private String s21 = "s21";
+ private String s22 = "s22";
+ private String s23 = "s23";
+ private String s24 = "s24";
+ private String s25 = "s25";
+ private String s26 = "s26";
+ private String s27 = "s27";
+ private String s28 = "s28";
+ private String s29 = "s29";
+ private String s30 = "s30";
+ private String s31 = "s31";
+ private String s32 = "s32";
+ private String s33 = "s33";
+ private String s34 = "s34";
+ private String s35 = "s35";
+ private String s36 = "s36";
+ private String s37 = "s37";
+ private String s38 = "s38";
+ private String s39 = "s39";
+ private String s40 = "s40";
+ private String s41 = "s41";
+ private String s42 = "s42";
+ private String s43 = "s43";
+ private String s44 = "s44";
+ private String s45 = "s45";
+ private String s46 = "s46";
+ private String s47 = "s47";
+ private String s48 = "s48";
+ private String s49 = "s49";
+ private String s50 = "s50";
+ private String s51 = "s51";
+ private String s52 = "s52";
+ private String s53 = "s53";
+ private String s54 = "s54";
+ private String s55 = "s55";
+ private String s56 = "s56";
+ private String s57 = "s57";
+ private String s58 = "s58";
+ private String s59 = "s59";
+ private String s60 = "s60";
+ private String s61 = "s61";
+ private String s62 = "s62";
+ private String s63 = "s63";
+ private String s64 = "s64";
+ private String s65 = "s65";
+ private String s66 = "s66";
+ private String s67 = "s67";
+ private String s68 = "s68";
+ private String s69 = "s69";
+ private String s70 = "s70";
+ private String s71 = "s71";
+ private String s72 = "s72";
+ private String s73 = "s73";
+ private String s74 = "s74";
+ private String s75 = "s75";
+ private String s76 = "s76";
+ private String s77 = "s77";
+ private String s78 = "s78";
+ private String s79 = "s79";
+ private String s80 = "s80";
+ private String s81 = "s81";
+ private String s82 = "s82";
+ private String s83 = "s83";
+ private String s84 = "s84";
+ private String s85 = "s85";
+ private String s86 = "s86";
+ private String s87 = "s87";
+ private String s88 = "s88";
+ private String s89 = "s89";
+ private String s90 = "s90";
+ private String s91 = "s91";
+ private String s92 = "s92";
+ private String s93 = "s93";
+ private String s94 = "s94";
+ private String s95 = "s95";
+ private String s96 = "s96";
+ private String s97 = "s97";
+ private String s98 = "s98";
+ private String s99 = "s99";
+ private String s100 = "s100";
+ private String s101 = "s101";
+ private String s102 = "s102";
+ private String s103 = "s103";
+ private String s104 = "s104";
+ private String s105 = "s105";
+ private String s106 = "s106";
+ private String s107 = "s107";
+ private String s108 = "s108";
+ private String s109 = "s109";
+ private String s110 = "s110";
+ private String s111 = "s111";
+ private String s112 = "s112";
+ private String s113 = "s113";
+ private String s114 = "s114";
+ private String s115 = "s115";
+ private String s116 = "s116";
+ private String s117 = "s117";
+ private String s118 = "s118";
+ private String s119 = "s119";
+ private String s120 = "s120";
+ private String s121 = "s121";
+ private String s122 = "s122";
+ private String s123 = "s123";
+ private String s124 = "s124";
+ private String s125 = "s125";
+ private String s126 = "s126";
+ private String s127 = "s127";
+ private String s128 = "s128";
+ private String s129 = "s129";
+ private String s130 = "s130";
+ private String s131 = "s131";
+ private String s132 = "s132";
+ private String s133 = "s133";
+ private String s134 = "s134";
+ private String s135 = "s135";
+ private String s136 = "s136";
+ private String s137 = "s137";
+ private String s138 = "s138";
+ private String s139 = "s139";
+ private String s140 = "s140";
+ private String s141 = "s141";
+ private String s142 = "s142";
+ private String s143 = "s143";
+ private String s144 = "s144";
+ private String s145 = "s145";
+ private String s146 = "s146";
+ private String s147 = "s147";
+ private String s148 = "s148";
+ private String s149 = "s149";
+ private String s150 = "s150";
+ private String s151 = "s151";
+ private String s152 = "s152";
+ private String s153 = "s153";
+ private String s154 = "s154";
+ private String s155 = "s155";
+ private String s156 = "s156";
+ private String s157 = "s157";
+ private String s158 = "s158";
+ private String s159 = "s159";
+ private String s160 = "s160";
+ private String s161 = "s161";
+ private String s162 = "s162";
+ private String s163 = "s163";
+ private String s164 = "s164";
+ private String s165 = "s165";
+ private String s166 = "s166";
+ private String s167 = "s167";
+ private String s168 = "s168";
+ private String s169 = "s169";
+ private String s170 = "s170";
+ private String s171 = "s171";
+ private String s172 = "s172";
+ private String s173 = "s173";
+ private String s174 = "s174";
+ private String s175 = "s175";
+ private String s176 = "s176";
+ private String s177 = "s177";
+ private String s178 = "s178";
+ private String s179 = "s179";
+ private String s180 = "s180";
+ private String s181 = "s181";
+ private String s182 = "s182";
+ private String s183 = "s183";
+ private String s184 = "s184";
+ private String s185 = "s185";
+ private String s186 = "s186";
+ private String s187 = "s187";
+ private String s188 = "s188";
+ private String s189 = "s189";
+ private String s190 = "s190";
+ private String s191 = "s191";
+ private String s192 = "s192";
+ private String s193 = "s193";
+ private String s194 = "s194";
+ private String s195 = "s195";
+ private String s196 = "s196";
+ private String s197 = "s197";
+ private String s198 = "s198";
+ private String s199 = "s199";
+ }
+
+ private static void use(int i) {
+ if (i == 0) {
+ System.out.println("foo");
+ }
+ }
+
+ private static void useString(String s) {
+ if (s.equals("ab")) {
+ System.out.println("bar");
+ }
+ }
+
+ public static int test() {
+ Inner inner = new Inner();
+ String s0 = inner.s0;
+ int i0 = s0 != null ? inner.i0 : 0;
+ String s1 = inner.s1;
+ int i1 = s1 != null ? inner.i1 : 0;
+ String s2 = inner.s2;
+ int i2 = s2 != null ? inner.i2 : 0;
+ String s3 = inner.s3;
+ int i3 = s3 != null ? inner.i3 : 0;
+ String s4 = inner.s4;
+ int i4 = s4 != null ? inner.i4 : 0;
+ String s5 = inner.s5;
+ int i5 = s5 != null ? inner.i5 : 0;
+ String s6 = inner.s6;
+ int i6 = s6 != null ? inner.i6 : 0;
+ String s7 = inner.s7;
+ int i7 = s7 != null ? inner.i7 : 0;
+ String s8 = inner.s8;
+ int i8 = s8 != null ? inner.i8 : 0;
+ String s9 = inner.s9;
+ int i9 = s9 != null ? inner.i9 : 0;
+ String s10 = inner.s10;
+ int i10 = s10 != null ? inner.i10 : 0;
+ String s11 = inner.s11;
+ int i11 = s11 != null ? inner.i11 : 0;
+ String s12 = inner.s12;
+ int i12 = s12 != null ? inner.i12 : 0;
+ String s13 = inner.s13;
+ int i13 = s13 != null ? inner.i13 : 0;
+ String s14 = inner.s14;
+ int i14 = s14 != null ? inner.i14 : 0;
+ String s15 = inner.s15;
+ int i15 = s15 != null ? inner.i15 : 0;
+ String s16 = inner.s16;
+ int i16 = s16 != null ? inner.i16 : 0;
+ String s17 = inner.s17;
+ int i17 = s17 != null ? inner.i17 : 0;
+ String s18 = inner.s18;
+ int i18 = s18 != null ? inner.i18 : 0;
+ String s19 = inner.s19;
+ int i19 = s19 != null ? inner.i19 : 0;
+ String s20 = inner.s20;
+ int i20 = s20 != null ? inner.i20 : 0;
+ String s21 = inner.s21;
+ int i21 = s21 != null ? inner.i21 : 0;
+ String s22 = inner.s22;
+ int i22 = s22 != null ? inner.i22 : 0;
+ String s23 = inner.s23;
+ int i23 = s23 != null ? inner.i23 : 0;
+ String s24 = inner.s24;
+ int i24 = s24 != null ? inner.i24 : 0;
+ String s25 = inner.s25;
+ int i25 = s25 != null ? inner.i25 : 0;
+ String s26 = inner.s26;
+ int i26 = s26 != null ? inner.i26 : 0;
+ String s27 = inner.s27;
+ int i27 = s27 != null ? inner.i27 : 0;
+ String s28 = inner.s28;
+ int i28 = s28 != null ? inner.i28 : 0;
+ String s29 = inner.s29;
+ int i29 = s29 != null ? inner.i29 : 0;
+ String s30 = inner.s30;
+ int i30 = s30 != null ? inner.i30 : 0;
+ String s31 = inner.s31;
+ int i31 = s31 != null ? inner.i31 : 0;
+ String s32 = inner.s32;
+ int i32 = s32 != null ? inner.i32 : 0;
+ String s33 = inner.s33;
+ int i33 = s33 != null ? inner.i33 : 0;
+ String s34 = inner.s34;
+ int i34 = s34 != null ? inner.i34 : 0;
+ String s35 = inner.s35;
+ int i35 = s35 != null ? inner.i35 : 0;
+ String s36 = inner.s36;
+ int i36 = s36 != null ? inner.i36 : 0;
+ String s37 = inner.s37;
+ int i37 = s37 != null ? inner.i37 : 0;
+ String s38 = inner.s38;
+ int i38 = s38 != null ? inner.i38 : 0;
+ String s39 = inner.s39;
+ int i39 = s39 != null ? inner.i39 : 0;
+ String s40 = inner.s40;
+ int i40 = s40 != null ? inner.i40 : 0;
+ String s41 = inner.s41;
+ int i41 = s41 != null ? inner.i41 : 0;
+ String s42 = inner.s42;
+ int i42 = s42 != null ? inner.i42 : 0;
+ String s43 = inner.s43;
+ int i43 = s43 != null ? inner.i43 : 0;
+ String s44 = inner.s44;
+ int i44 = s44 != null ? inner.i44 : 0;
+ String s45 = inner.s45;
+ int i45 = s45 != null ? inner.i45 : 0;
+ String s46 = inner.s46;
+ int i46 = s46 != null ? inner.i46 : 0;
+ String s47 = inner.s47;
+ int i47 = s47 != null ? inner.i47 : 0;
+ String s48 = inner.s48;
+ int i48 = s48 != null ? inner.i48 : 0;
+ String s49 = inner.s49;
+ int i49 = s49 != null ? inner.i49 : 0;
+ String s50 = inner.s50;
+ int i50 = s50 != null ? inner.i50 : 0;
+ String s51 = inner.s51;
+ int i51 = s51 != null ? inner.i51 : 0;
+ String s52 = inner.s52;
+ int i52 = s52 != null ? inner.i52 : 0;
+ String s53 = inner.s53;
+ int i53 = s53 != null ? inner.i53 : 0;
+ String s54 = inner.s54;
+ int i54 = s54 != null ? inner.i54 : 0;
+ String s55 = inner.s55;
+ int i55 = s55 != null ? inner.i55 : 0;
+ String s56 = inner.s56;
+ int i56 = s56 != null ? inner.i56 : 0;
+ String s57 = inner.s57;
+ int i57 = s57 != null ? inner.i57 : 0;
+ String s58 = inner.s58;
+ int i58 = s58 != null ? inner.i58 : 0;
+ String s59 = inner.s59;
+ int i59 = s59 != null ? inner.i59 : 0;
+ String s60 = inner.s60;
+ int i60 = s60 != null ? inner.i60 : 0;
+ String s61 = inner.s61;
+ int i61 = s61 != null ? inner.i61 : 0;
+ String s62 = inner.s62;
+ int i62 = s62 != null ? inner.i62 : 0;
+ String s63 = inner.s63;
+ int i63 = s63 != null ? inner.i63 : 0;
+ String s64 = inner.s64;
+ int i64 = s64 != null ? inner.i64 : 0;
+ String s65 = inner.s65;
+ int i65 = s65 != null ? inner.i65 : 0;
+ String s66 = inner.s66;
+ int i66 = s66 != null ? inner.i66 : 0;
+ String s67 = inner.s67;
+ int i67 = s67 != null ? inner.i67 : 0;
+ String s68 = inner.s68;
+ int i68 = s68 != null ? inner.i68 : 0;
+ String s69 = inner.s69;
+ int i69 = s69 != null ? inner.i69 : 0;
+ String s70 = inner.s70;
+ int i70 = s70 != null ? inner.i70 : 0;
+ String s71 = inner.s71;
+ int i71 = s71 != null ? inner.i71 : 0;
+ String s72 = inner.s72;
+ int i72 = s72 != null ? inner.i72 : 0;
+ String s73 = inner.s73;
+ int i73 = s73 != null ? inner.i73 : 0;
+ String s74 = inner.s74;
+ int i74 = s74 != null ? inner.i74 : 0;
+ String s75 = inner.s75;
+ int i75 = s75 != null ? inner.i75 : 0;
+ String s76 = inner.s76;
+ int i76 = s76 != null ? inner.i76 : 0;
+ String s77 = inner.s77;
+ int i77 = s77 != null ? inner.i77 : 0;
+ String s78 = inner.s78;
+ int i78 = s78 != null ? inner.i78 : 0;
+ String s79 = inner.s79;
+ int i79 = s79 != null ? inner.i79 : 0;
+ String s80 = inner.s80;
+ int i80 = s80 != null ? inner.i80 : 0;
+ String s81 = inner.s81;
+ int i81 = s81 != null ? inner.i81 : 0;
+ String s82 = inner.s82;
+ int i82 = s82 != null ? inner.i82 : 0;
+ String s83 = inner.s83;
+ int i83 = s83 != null ? inner.i83 : 0;
+ String s84 = inner.s84;
+ int i84 = s84 != null ? inner.i84 : 0;
+ String s85 = inner.s85;
+ int i85 = s85 != null ? inner.i85 : 0;
+ String s86 = inner.s86;
+ int i86 = s86 != null ? inner.i86 : 0;
+ String s87 = inner.s87;
+ int i87 = s87 != null ? inner.i87 : 0;
+ String s88 = inner.s88;
+ int i88 = s88 != null ? inner.i88 : 0;
+ String s89 = inner.s89;
+ int i89 = s89 != null ? inner.i89 : 0;
+ String s90 = inner.s90;
+ int i90 = s90 != null ? inner.i90 : 0;
+ String s91 = inner.s91;
+ int i91 = s91 != null ? inner.i91 : 0;
+ String s92 = inner.s92;
+ int i92 = s92 != null ? inner.i92 : 0;
+ String s93 = inner.s93;
+ int i93 = s93 != null ? inner.i93 : 0;
+ String s94 = inner.s94;
+ int i94 = s94 != null ? inner.i94 : 0;
+ String s95 = inner.s95;
+ int i95 = s95 != null ? inner.i95 : 0;
+ String s96 = inner.s96;
+ int i96 = s96 != null ? inner.i96 : 0;
+ String s97 = inner.s97;
+ int i97 = s97 != null ? inner.i97 : 0;
+ String s98 = inner.s98;
+ int i98 = s98 != null ? inner.i98 : 0;
+ String s99 = inner.s99;
+ int i99 = s99 != null ? inner.i99 : 0;
+ String s100 = inner.s100;
+ int i100 = s100 != null ? inner.i100 : 0;
+ String s101 = inner.s101;
+ int i101 = s101 != null ? inner.i101 : 0;
+ String s102 = inner.s102;
+ int i102 = s102 != null ? inner.i102 : 0;
+ String s103 = inner.s103;
+ int i103 = s103 != null ? inner.i103 : 0;
+ String s104 = inner.s104;
+ int i104 = s104 != null ? inner.i104 : 0;
+ String s105 = inner.s105;
+ int i105 = s105 != null ? inner.i105 : 0;
+ String s106 = inner.s106;
+ int i106 = s106 != null ? inner.i106 : 0;
+ String s107 = inner.s107;
+ int i107 = s107 != null ? inner.i107 : 0;
+ String s108 = inner.s108;
+ int i108 = s108 != null ? inner.i108 : 0;
+ String s109 = inner.s109;
+ int i109 = s109 != null ? inner.i109 : 0;
+ String s110 = inner.s110;
+ int i110 = s110 != null ? inner.i110 : 0;
+ String s111 = inner.s111;
+ int i111 = s111 != null ? inner.i111 : 0;
+ String s112 = inner.s112;
+ int i112 = s112 != null ? inner.i112 : 0;
+ String s113 = inner.s113;
+ int i113 = s113 != null ? inner.i113 : 0;
+ String s114 = inner.s114;
+ int i114 = s114 != null ? inner.i114 : 0;
+ String s115 = inner.s115;
+ int i115 = s115 != null ? inner.i115 : 0;
+ String s116 = inner.s116;
+ int i116 = s116 != null ? inner.i116 : 0;
+ String s117 = inner.s117;
+ int i117 = s117 != null ? inner.i117 : 0;
+ String s118 = inner.s118;
+ int i118 = s118 != null ? inner.i118 : 0;
+ String s119 = inner.s119;
+ int i119 = s119 != null ? inner.i119 : 0;
+ String s120 = inner.s120;
+ int i120 = s120 != null ? inner.i120 : 0;
+ String s121 = inner.s121;
+ int i121 = s121 != null ? inner.i121 : 0;
+ String s122 = inner.s122;
+ int i122 = s122 != null ? inner.i122 : 0;
+ String s123 = inner.s123;
+ int i123 = s123 != null ? inner.i123 : 0;
+ String s124 = inner.s124;
+ int i124 = s124 != null ? inner.i124 : 0;
+ String s125 = inner.s125;
+ int i125 = s125 != null ? inner.i125 : 0;
+ String s126 = inner.s126;
+ int i126 = s126 != null ? inner.i126 : 0;
+ String s127 = inner.s127;
+ int i127 = s127 != null ? inner.i127 : 0;
+ String s128 = inner.s128;
+ int i128 = s128 != null ? inner.i128 : 0;
+ String s129 = inner.s129;
+ int i129 = s129 != null ? inner.i129 : 0;
+ String s130 = inner.s130;
+ int i130 = s130 != null ? inner.i130 : 0;
+ String s131 = inner.s131;
+ int i131 = s131 != null ? inner.i131 : 0;
+ String s132 = inner.s132;
+ int i132 = s132 != null ? inner.i132 : 0;
+ String s133 = inner.s133;
+ int i133 = s133 != null ? inner.i133 : 0;
+ String s134 = inner.s134;
+ int i134 = s134 != null ? inner.i134 : 0;
+ String s135 = inner.s135;
+ int i135 = s135 != null ? inner.i135 : 0;
+ String s136 = inner.s136;
+ int i136 = s136 != null ? inner.i136 : 0;
+ String s137 = inner.s137;
+ int i137 = s137 != null ? inner.i137 : 0;
+ String s138 = inner.s138;
+ int i138 = s138 != null ? inner.i138 : 0;
+ String s139 = inner.s139;
+ int i139 = s139 != null ? inner.i139 : 0;
+ String s140 = inner.s140;
+ int i140 = s140 != null ? inner.i140 : 0;
+ String s141 = inner.s141;
+ int i141 = s141 != null ? inner.i141 : 0;
+ String s142 = inner.s142;
+ int i142 = s142 != null ? inner.i142 : 0;
+ String s143 = inner.s143;
+ int i143 = s143 != null ? inner.i143 : 0;
+ String s144 = inner.s144;
+ int i144 = s144 != null ? inner.i144 : 0;
+ String s145 = inner.s145;
+ int i145 = s145 != null ? inner.i145 : 0;
+ String s146 = inner.s146;
+ int i146 = s146 != null ? inner.i146 : 0;
+ String s147 = inner.s147;
+ int i147 = s147 != null ? inner.i147 : 0;
+ String s148 = inner.s148;
+ int i148 = s148 != null ? inner.i148 : 0;
+ String s149 = inner.s149;
+ int i149 = s149 != null ? inner.i149 : 0;
+ String s150 = inner.s150;
+ int i150 = s150 != null ? inner.i150 : 0;
+ String s151 = inner.s151;
+ int i151 = s151 != null ? inner.i151 : 0;
+ String s152 = inner.s152;
+ int i152 = s152 != null ? inner.i152 : 0;
+ String s153 = inner.s153;
+ int i153 = s153 != null ? inner.i153 : 0;
+ String s154 = inner.s154;
+ int i154 = s154 != null ? inner.i154 : 0;
+ String s155 = inner.s155;
+ int i155 = s155 != null ? inner.i155 : 0;
+ String s156 = inner.s156;
+ int i156 = s156 != null ? inner.i156 : 0;
+ String s157 = inner.s157;
+ int i157 = s157 != null ? inner.i157 : 0;
+ String s158 = inner.s158;
+ int i158 = s158 != null ? inner.i158 : 0;
+ String s159 = inner.s159;
+ int i159 = s159 != null ? inner.i159 : 0;
+ String s160 = inner.s160;
+ int i160 = s160 != null ? inner.i160 : 0;
+ String s161 = inner.s161;
+ int i161 = s161 != null ? inner.i161 : 0;
+ String s162 = inner.s162;
+ int i162 = s162 != null ? inner.i162 : 0;
+ String s163 = inner.s163;
+ int i163 = s163 != null ? inner.i163 : 0;
+ String s164 = inner.s164;
+ int i164 = s164 != null ? inner.i164 : 0;
+ String s165 = inner.s165;
+ int i165 = s165 != null ? inner.i165 : 0;
+ String s166 = inner.s166;
+ int i166 = s166 != null ? inner.i166 : 0;
+ String s167 = inner.s167;
+ int i167 = s167 != null ? inner.i167 : 0;
+ String s168 = inner.s168;
+ int i168 = s168 != null ? inner.i168 : 0;
+ String s169 = inner.s169;
+ int i169 = s169 != null ? inner.i169 : 0;
+ String s170 = inner.s170;
+ int i170 = s170 != null ? inner.i170 : 0;
+ String s171 = inner.s171;
+ int i171 = s171 != null ? inner.i171 : 0;
+ String s172 = inner.s172;
+ int i172 = s172 != null ? inner.i172 : 0;
+ String s173 = inner.s173;
+ int i173 = s173 != null ? inner.i173 : 0;
+ String s174 = inner.s174;
+ int i174 = s174 != null ? inner.i174 : 0;
+ String s175 = inner.s175;
+ int i175 = s175 != null ? inner.i175 : 0;
+ String s176 = inner.s176;
+ int i176 = s176 != null ? inner.i176 : 0;
+ String s177 = inner.s177;
+ int i177 = s177 != null ? inner.i177 : 0;
+ String s178 = inner.s178;
+ int i178 = s178 != null ? inner.i178 : 0;
+ String s179 = inner.s179;
+ int i179 = s179 != null ? inner.i179 : 0;
+ String s180 = inner.s180;
+ int i180 = s180 != null ? inner.i180 : 0;
+ String s181 = inner.s181;
+ int i181 = s181 != null ? inner.i181 : 0;
+ String s182 = inner.s182;
+ int i182 = s182 != null ? inner.i182 : 0;
+ String s183 = inner.s183;
+ int i183 = s183 != null ? inner.i183 : 0;
+ String s184 = inner.s184;
+ int i184 = s184 != null ? inner.i184 : 0;
+ String s185 = inner.s185;
+ int i185 = s185 != null ? inner.i185 : 0;
+ String s186 = inner.s186;
+ int i186 = s186 != null ? inner.i186 : 0;
+ String s187 = inner.s187;
+ int i187 = s187 != null ? inner.i187 : 0;
+ String s188 = inner.s188;
+ int i188 = s188 != null ? inner.i188 : 0;
+ String s189 = inner.s189;
+ int i189 = s189 != null ? inner.i189 : 0;
+ String s190 = inner.s190;
+ int i190 = s190 != null ? inner.i190 : 0;
+ String s191 = inner.s191;
+ int i191 = s191 != null ? inner.i191 : 0;
+ String s192 = inner.s192;
+ int i192 = s192 != null ? inner.i192 : 0;
+ String s193 = inner.s193;
+ int i193 = s193 != null ? inner.i193 : 0;
+ String s194 = inner.s194;
+ int i194 = s194 != null ? inner.i194 : 0;
+ String s195 = inner.s195;
+ int i195 = s195 != null ? inner.i195 : 0;
+ String s196 = inner.s196;
+ int i196 = s196 != null ? inner.i196 : 0;
+ String s197 = inner.s197;
+ int i197 = s197 != null ? inner.i197 : 0;
+ String s198 = inner.s198;
+ int i198 = s198 != null ? inner.i198 : 0;
+ String s199 = inner.s199;
+ int i199 = s199 != null ? inner.i199 : 0;
+
+ useString(s0);
+ use(i0);
+ useString(s1);
+ use(i1);
+ useString(s2);
+ use(i2);
+ useString(s3);
+ use(i3);
+ useString(s4);
+ use(i4);
+ useString(s5);
+ use(i5);
+ useString(s6);
+ use(i6);
+ useString(s7);
+ use(i7);
+ useString(s8);
+ use(i8);
+ useString(s9);
+ use(i9);
+ useString(s10);
+ use(i10);
+ useString(s11);
+ use(i11);
+ useString(s12);
+ use(i12);
+ useString(s13);
+ use(i13);
+ useString(s14);
+ use(i14);
+ useString(s15);
+ use(i15);
+ useString(s16);
+ use(i16);
+ useString(s17);
+ use(i17);
+ useString(s18);
+ use(i18);
+ useString(s19);
+ use(i19);
+ useString(s20);
+ use(i20);
+ useString(s21);
+ use(i21);
+ useString(s22);
+ use(i22);
+ useString(s23);
+ use(i23);
+ useString(s24);
+ use(i24);
+ useString(s25);
+ use(i25);
+ useString(s26);
+ use(i26);
+ useString(s27);
+ use(i27);
+ useString(s28);
+ use(i28);
+ useString(s29);
+ use(i29);
+ useString(s30);
+ use(i30);
+ useString(s31);
+ use(i31);
+ useString(s32);
+ use(i32);
+ useString(s33);
+ use(i33);
+ useString(s34);
+ use(i34);
+ useString(s35);
+ use(i35);
+ useString(s36);
+ use(i36);
+ useString(s37);
+ use(i37);
+ useString(s38);
+ use(i38);
+ useString(s39);
+ use(i39);
+ useString(s40);
+ use(i40);
+ useString(s41);
+ use(i41);
+ useString(s42);
+ use(i42);
+ useString(s43);
+ use(i43);
+ useString(s44);
+ use(i44);
+ useString(s45);
+ use(i45);
+ useString(s46);
+ use(i46);
+ useString(s47);
+ use(i47);
+ useString(s48);
+ use(i48);
+ useString(s49);
+ use(i49);
+ useString(s50);
+ use(i50);
+ useString(s51);
+ use(i51);
+ useString(s52);
+ use(i52);
+ useString(s53);
+ use(i53);
+ useString(s54);
+ use(i54);
+ useString(s55);
+ use(i55);
+ useString(s56);
+ use(i56);
+ useString(s57);
+ use(i57);
+ useString(s58);
+ use(i58);
+ useString(s59);
+ use(i59);
+ useString(s60);
+ use(i60);
+ useString(s61);
+ use(i61);
+ useString(s62);
+ use(i62);
+ useString(s63);
+ use(i63);
+ useString(s64);
+ use(i64);
+ useString(s65);
+ use(i65);
+ useString(s66);
+ use(i66);
+ useString(s67);
+ use(i67);
+ useString(s68);
+ use(i68);
+ useString(s69);
+ use(i69);
+ useString(s70);
+ use(i70);
+ useString(s71);
+ use(i71);
+ useString(s72);
+ use(i72);
+ useString(s73);
+ use(i73);
+ useString(s74);
+ use(i74);
+ useString(s75);
+ use(i75);
+ useString(s76);
+ use(i76);
+ useString(s77);
+ use(i77);
+ useString(s78);
+ use(i78);
+ useString(s79);
+ use(i79);
+ useString(s80);
+ use(i80);
+ useString(s81);
+ use(i81);
+ useString(s82);
+ use(i82);
+ useString(s83);
+ use(i83);
+ useString(s84);
+ use(i84);
+ useString(s85);
+ use(i85);
+ useString(s86);
+ use(i86);
+ useString(s87);
+ use(i87);
+ useString(s88);
+ use(i88);
+ useString(s89);
+ use(i89);
+ useString(s90);
+ use(i90);
+ useString(s91);
+ use(i91);
+ useString(s92);
+ use(i92);
+ useString(s93);
+ use(i93);
+ useString(s94);
+ use(i94);
+ useString(s95);
+ use(i95);
+ useString(s96);
+ use(i96);
+ useString(s97);
+ use(i97);
+ useString(s98);
+ use(i98);
+ useString(s99);
+ use(i99);
+ useString(s100);
+ use(i100);
+ useString(s101);
+ use(i101);
+ useString(s102);
+ use(i102);
+ useString(s103);
+ use(i103);
+ useString(s104);
+ use(i104);
+ useString(s105);
+ use(i105);
+ useString(s106);
+ use(i106);
+ useString(s107);
+ use(i107);
+ useString(s108);
+ use(i108);
+ useString(s109);
+ use(i109);
+ useString(s110);
+ use(i110);
+ useString(s111);
+ use(i111);
+ useString(s112);
+ use(i112);
+ useString(s113);
+ use(i113);
+ useString(s114);
+ use(i114);
+ useString(s115);
+ use(i115);
+ useString(s116);
+ use(i116);
+ useString(s117);
+ use(i117);
+ useString(s118);
+ use(i118);
+ useString(s119);
+ use(i119);
+ useString(s120);
+ use(i120);
+ useString(s121);
+ use(i121);
+ useString(s122);
+ use(i122);
+ useString(s123);
+ use(i123);
+ useString(s124);
+ use(i124);
+ useString(s125);
+ use(i125);
+ useString(s126);
+ use(i126);
+ useString(s127);
+ use(i127);
+ useString(s128);
+ use(i128);
+ useString(s129);
+ use(i129);
+ useString(s130);
+ use(i130);
+ useString(s131);
+ use(i131);
+ useString(s132);
+ use(i132);
+ useString(s133);
+ use(i133);
+ useString(s134);
+ use(i134);
+ useString(s135);
+ use(i135);
+ useString(s136);
+ use(i136);
+ useString(s137);
+ use(i137);
+ useString(s138);
+ use(i138);
+ useString(s139);
+ use(i139);
+ useString(s140);
+ use(i140);
+ useString(s141);
+ use(i141);
+ useString(s142);
+ use(i142);
+ useString(s143);
+ use(i143);
+ useString(s144);
+ use(i144);
+ useString(s145);
+ use(i145);
+ useString(s146);
+ use(i146);
+ useString(s147);
+ use(i147);
+ useString(s148);
+ use(i148);
+ useString(s149);
+ use(i149);
+ useString(s150);
+ use(i150);
+ useString(s151);
+ use(i151);
+ useString(s152);
+ use(i152);
+ useString(s153);
+ use(i153);
+ useString(s154);
+ use(i154);
+ useString(s155);
+ use(i155);
+ useString(s156);
+ use(i156);
+ useString(s157);
+ use(i157);
+ useString(s158);
+ use(i158);
+ useString(s159);
+ use(i159);
+ useString(s160);
+ use(i160);
+ useString(s161);
+ use(i161);
+ useString(s162);
+ use(i162);
+ useString(s163);
+ use(i163);
+ useString(s164);
+ use(i164);
+ useString(s165);
+ use(i165);
+ useString(s166);
+ use(i166);
+ useString(s167);
+ use(i167);
+ useString(s168);
+ use(i168);
+ useString(s169);
+ use(i169);
+ useString(s170);
+ use(i170);
+ useString(s171);
+ use(i171);
+ useString(s172);
+ use(i172);
+ useString(s173);
+ use(i173);
+ useString(s174);
+ use(i174);
+ useString(s175);
+ use(i175);
+ useString(s176);
+ use(i176);
+ useString(s177);
+ use(i177);
+ useString(s178);
+ use(i178);
+ useString(s179);
+ use(i179);
+ useString(s180);
+ use(i180);
+ useString(s181);
+ use(i181);
+ useString(s182);
+ use(i182);
+ useString(s183);
+ use(i183);
+ useString(s184);
+ use(i184);
+ useString(s185);
+ use(i185);
+ useString(s186);
+ use(i186);
+ useString(s187);
+ use(i187);
+ useString(s188);
+ use(i188);
+ useString(s189);
+ use(i189);
+ useString(s190);
+ use(i190);
+ useString(s191);
+ use(i191);
+ useString(s192);
+ use(i192);
+ useString(s193);
+ use(i193);
+ useString(s194);
+ use(i194);
+ useString(s195);
+ use(i195);
+ useString(s196);
+ use(i196);
+ useString(s197);
+ use(i197);
+ useString(s198);
+ use(i198);
+ useString(s199);
+ use(i199);
+ return i199;
+ }
+
+
+ public static void main(String[] args) {
+ Regress instance = new Regress();
+ instance.test();
+ }
+}
diff --git a/src/test/examplesAndroidN/dexsplitsample/Class3.java b/src/test/examplesAndroidN/dexsplitsample/Class3.java
index 98469a5..7dba306 100644
--- a/src/test/examplesAndroidN/dexsplitsample/Class3.java
+++ b/src/test/examplesAndroidN/dexsplitsample/Class3.java
@@ -5,6 +5,9 @@
package dexsplitsample;
public class Class3 extends Class1 {
+ // Instantiate Class1 to prevent it from being merged into Class3.
+ private static final Class1 obj = new Class1();
+
public static void main(String[] args) {
Class3 clazz = new Class3();
if (clazz.getClass1String() != "Class1String") {
diff --git a/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java b/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java
new file mode 100644
index 0000000..10f995d
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class MergeDefaultMethodIntoClassTest {
+
+ public static void main(String[] args) {
+ // Note: Important that the static type of [obj] is A, such that the call to f becomes an
+ // invoke-interface instruction and not invoke-virtual instruction.
+ A obj = new B();
+ obj.f();
+ }
+
+ public interface A {
+ default void f() {
+ System.out.println("In A.f");
+ }
+ }
+
+ public static class B implements A {}
+}
diff --git a/src/test/examplesAndroidO/classmerging/keep-rules.txt b/src/test/examplesAndroidO/classmerging/keep-rules.txt
index 9868dcf..fc91808 100644
--- a/src/test/examplesAndroidO/classmerging/keep-rules.txt
+++ b/src/test/examplesAndroidO/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
-keep public class classmerging.LambdaRewritingTest {
public static void main(...);
}
+-keep public class classmerging.MergeDefaultMethodIntoClassTest {
+ public static void main(...);
+}
# TODO(herhut): Consider supporting merging of inner-class attributes.
# -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 2f45ae2..d79aaf0 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -116,9 +116,14 @@
messageParts);
}
+ public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, int index, Path path,
+ int lineStart, int columnStart, String... messageParts) {
+ return checkDiagnostic(diagnostics.get(index), path, lineStart, columnStart, messageParts);
+ }
+
public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, Path path,
int lineStart, int columnStart, String... messageParts) {
assertEquals(1, diagnostics.size());
- return checkDiagnostic(diagnostics.get(0), path, lineStart, columnStart, messageParts);
+ return checkDiagnostics(diagnostics, 0, path, lineStart, columnStart, messageParts);
}
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 36d924f..221d427 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -74,6 +74,7 @@
"regress_70736958.Test",
"regress_70737019.Test",
"regress_72361252.Test",
+ "regress_110373181.Regress",
"memberrebinding2.Memberrebinding",
"memberrebinding3.Memberrebinding",
"minification.Minification",
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d5d0558..10f62a1 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1546,6 +1546,12 @@
return builder;
}
+ public static R8Command.Builder allowTestProguardOptions(
+ R8Command.Builder builder) {
+ builder.allowTestProguardOptions();
+ return builder;
+ }
+
public static AndroidApp getApp(BaseCommand command) {
return command.getInputApp();
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
index 027b7e8..53a8c31 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
@@ -171,10 +171,13 @@
assertNotPublic(dexInspector, Base.class,
new MethodSignature("foo2", STRING, ImmutableList.of()));
- // Sub1#bar1(int) can't be publicized due to Base#bar1(int).
- assertNotPublic(dexInspector, Sub1.class,
+ // Sub?#bar1(int) can be publicized as they don't bother each other.
+ assertPublic(dexInspector, Sub1.class,
+ new MethodSignature("bar1", STRING, ImmutableList.of("int")));
+ assertPublic(dexInspector, Sub2.class,
new MethodSignature("bar1", STRING, ImmutableList.of("int")));
+ // Sub2#bar2(int) is unique throughout the hierarchy, hence publicized.
assertPublic(dexInspector, Sub2.class,
new MethodSignature("bar2", STRING, ImmutableList.of("int")));
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
index fd1fd1c..6325c78 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
@@ -30,19 +30,10 @@
return foo2();
}
- private synchronized String bar1(int i) {
- throw new AssertionError("Sub1#bar1(int) will not use this signature.");
- }
-
public void dump() {
System.out.println(foo());
System.out.println(foo1());
System.out.println(foo2());
- try {
- bar1(0);
- } catch (AssertionError e) {
- // expected
- }
}
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
index 4cbbf11..317aaff 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
@@ -22,11 +22,7 @@
public void dump() {
System.out.println(foo1());
System.out.println(foo1(0));
- try {
- System.out.println(bar1(0));
- } catch (Throwable e) {
- System.out.println(e.getClass().getName());
- }
+ System.out.println(bar1(0));
}
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
index 8c4e01e..039e3ac 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
@@ -10,6 +10,14 @@
return "Sub2::foo2()";
}
+ private synchronized String bar1(int i) {
+ return "Sub2::bar1(" + i + ")";
+ }
+
+ public String pBar1() {
+ return bar1(1);
+ }
+
private synchronized String bar2(int i) {
return "Sub2::bar2(" + i + ")";
}
@@ -23,6 +31,11 @@
System.out.println(foo2());
System.out.println(foo2(0));
System.out.println(bar2(0));
+ try {
+ bar1(0);
+ } catch (AssertionError e) {
+ // expected
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 59accb3..f28c86f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.smali.SmaliBuilder.buildCode;
import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
@@ -22,8 +23,11 @@
import com.android.tools.r8.code.MoveException;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -183,6 +187,22 @@
}
@Test
+ public void testFieldCollision() throws Exception {
+ String main = "classmerging.FieldCollisionTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("FieldCollisionTest.class"),
+ CF_DIR.resolve("FieldCollisionTest$A.class"),
+ CF_DIR.resolve("FieldCollisionTest$B.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.FieldCollisionTest",
+ "classmerging.FieldCollisionTest$B");
+ runTest(main, programFiles, preservedClassNames::contains);
+ }
+
+ @Test
public void testLambdaRewriting() throws Exception {
String main = "classmerging.LambdaRewritingTest";
Path[] programFiles =
@@ -206,6 +226,31 @@
}
@Test
+ public void testMethodCollision() throws Exception {
+ String main = "classmerging.MethodCollisionTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("MethodCollisionTest.class"),
+ CF_DIR.resolve("MethodCollisionTest$A.class"),
+ CF_DIR.resolve("MethodCollisionTest$B.class"),
+ CF_DIR.resolve("MethodCollisionTest$C.class"),
+ CF_DIR.resolve("MethodCollisionTest$D.class")
+ };
+ // TODO(christofferqa): Currently we do not allow merging A into B because we find a collision.
+ // However, we are free to change the names of private methods, so we should handle them similar
+ // to fields (i.e., we should allow merging A into B). This would also improve the performance
+ // of the collision detector, because it would only have to consider non-private methods.
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.MethodCollisionTest",
+ "classmerging.MethodCollisionTest$A",
+ "classmerging.MethodCollisionTest$B",
+ "classmerging.MethodCollisionTest$C",
+ "classmerging.MethodCollisionTest$D");
+ runTest(main, programFiles, preservedClassNames::contains);
+ }
+
+ @Test
public void testPinnedParameterTypes() throws Exception {
String main = "classmerging.PinnedParameterTypesTest";
Path[] programFiles =
@@ -343,47 +388,57 @@
"classmerging.D",
"classmerging.F");
- SmaliBuilder smaliBuilder = new SmaliBuilder();
+ JasminBuilder jasminBuilder = new JasminBuilder();
- smaliBuilder.addClass(main);
- smaliBuilder.addMainMethod(
- 2,
+ ClassBuilder classBuilder = jasminBuilder.addClass(main);
+ classBuilder.addMainMethod(
+ ".limit locals 1",
+ ".limit stack 2",
// Instantiate B so that it is not merged into C.
- "new-instance v1, Lclassmerging/B;",
- "invoke-direct {v1}, Lclassmerging/B;-><init>()V",
- "invoke-virtual {v1}, Lclassmerging/B;->m()V",
+ "new classmerging/B",
+ "dup",
+ "invokespecial classmerging/B/<init>()V",
+ "invokevirtual classmerging/B/m()V",
// Instantiate D so that it is not merged into E.
- "new-instance v1, Lclassmerging/D;",
- "invoke-direct {v1}, Lclassmerging/D;-><init>()V",
- "invoke-virtual {v1}, Lclassmerging/D;->m()V",
+ "new classmerging/D",
+ "dup",
+ "invokespecial classmerging/D/<init>()V",
+ "invokevirtual classmerging/D/m()V",
// Start the actual testing.
- "new-instance v1, Lclassmerging/F;",
- "invoke-direct {v1}, Lclassmerging/F;-><init>()V",
- "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnB()V",
- "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnC()V",
- "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnD()V",
- "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnE()V",
- "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnF()V",
+ "new classmerging/F",
+ "dup",
+ "invokespecial classmerging/F/<init>()V",
+ "dup",
+ "invokevirtual classmerging/F/invokeMethodOnB()V",
+ "dup",
+ "invokevirtual classmerging/F/invokeMethodOnC()V",
+ "dup",
+ "invokevirtual classmerging/F/invokeMethodOnD()V",
+ "dup",
+ "invokevirtual classmerging/F/invokeMethodOnE()V",
+ "dup",
+ "invokevirtual classmerging/F/invokeMethodOnF()V",
+ "dup",
// The method invokeMethodOnA() should yield a NoSuchMethodError.
- ":try_start",
- "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnA()V",
- ":try_end",
- "return-void",
- ".catch Ljava/lang/NoSuchMethodError; {:try_start .. :try_end} :catch",
- ":catch",
- smaliCodeForPrinting("NoSuchMethodError", 0, 1),
- "return-void");
+ "try_start:",
+ "invokevirtual classmerging/F/invokeMethodOnA()V",
+ "try_end:",
+ "return",
+ "catch:",
+ jasminCodeForPrinting("NoSuchMethodError"),
+ "return",
+ ".catch java/lang/NoSuchMethodError from try_start to try_end using catch");
// Class A deliberately has no method m. We need to make sure that the "invoke-super A.m()"
// instruction in class F is not rewritten into something that does not throw.
- smaliBuilder.addClass("classmerging.A");
- smaliBuilder.addDefaultConstructor();
+ classBuilder = jasminBuilder.addClass("classmerging.A");
+ classBuilder.addDefaultConstructor();
// Class B declares a virtual method m() that prints "In B.m()".
- smaliBuilder.addClass("classmerging.B", "classmerging.A");
- smaliBuilder.addDefaultConstructor();
- smaliBuilder.addInstanceMethod(
- "void", "m", 2, smaliCodeForPrinting("In B.m()", 0, 1), "return-void");
+ classBuilder = jasminBuilder.addClass("classmerging.B", "classmerging.A");
+ classBuilder.addDefaultConstructor();
+ classBuilder.addVirtualMethod(
+ "m", "V", ".limit locals 1", ".limit stack 2", jasminCodeForPrinting("In B.m()"), "return");
// Class C, D, and E declare a virtual method m() that prints "In C.m()", "In D.m()", and
// "In E.m()", respectively.
@@ -391,17 +446,24 @@
new String[][] {new String[] {"C", "B"}, new String[] {"D", "C"}, new String[] {"E", "D"}};
for (String[] pair : pairs) {
String name = pair[0], superName = pair[1];
- smaliBuilder.addClass("classmerging." + name, "classmerging." + superName);
- smaliBuilder.addDefaultConstructor();
- smaliBuilder.addInstanceMethod(
- "void",
+ classBuilder = jasminBuilder.addClass("classmerging." + name, "classmerging." + superName);
+ classBuilder.addDefaultConstructor();
+ classBuilder.addVirtualMethod(
"m",
- 2,
- smaliCodeForPrinting("In " + name + ".m()", 0, 1),
- buildCode("invoke-super {p0}, Lclassmerging/" + superName + ";->m()V", "return-void"));
+ "V",
+ ".limit locals 1",
+ ".limit stack 2",
+ jasminCodeForPrinting("In " + name + ".m()"),
+ "aload_0",
+ "invokespecial classmerging/" + superName + "/m()V",
+ "return");
}
// Class F declares a virtual method m that throws an exception (it is expected to be dead).
+ //
+ // Note that F is generated from smali since it is the only way to generate an instruction on
+ // the form "invoke-super F.m()".
+ SmaliBuilder smaliBuilder = new SmaliBuilder();
smaliBuilder.addClass("classmerging.F", "classmerging.E");
smaliBuilder.addDefaultConstructor();
smaliBuilder.addInstanceMethod(
@@ -411,7 +473,6 @@
"new-instance v1, Ljava/lang/Exception;",
"invoke-direct {v1}, Ljava/lang/Exception;-><init>()V",
"throw v1");
-
// Add methods to F with an "invoke-super X.m()" instruction for X in {A, B, C, D, E, F}.
for (String type : ImmutableList.of("A", "B", "C", "D", "E", "F")) {
String code =
@@ -420,24 +481,23 @@
}
// Build app.
- AndroidApp.Builder builder = AndroidApp.builder();
- builder.addDexProgramData(smaliBuilder.compile(), Origin.unknown());
+ AndroidApp.Builder appBuilder = AndroidApp.builder();
+ appBuilder.addClassProgramData(jasminBuilder.buildClasses());
+ appBuilder.addDexProgramData(smaliBuilder.compile(), Origin.unknown());
// Run test.
runTestOnInput(
main,
- builder.build(),
+ appBuilder.build(),
preservedClassNames::contains,
String.format("-keep class %s { public static void main(...); }", main));
}
- private static String smaliCodeForPrinting(String message, int reg0, int reg1) {
+ private static String jasminCodeForPrinting(String message) {
return buildCode(
- String.format("sget-object v%d, Ljava/lang/System;->out:Ljava/io/PrintStream;", reg0),
- String.format("const-string v1, \"%s\"", message),
- String.format(
- "invoke-virtual {v%d, v%d}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
- reg0, reg1));
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ String.format("ldc \"%s\"", message),
+ "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V");
}
@Test
@@ -496,6 +556,58 @@
}
@Test
+ public void testMergeDefaultMethodIntoClass() throws Exception {
+ String main = "classmerging.MergeDefaultMethodIntoClassTest";
+ Path[] programFiles =
+ new Path[] {
+ JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest.class"),
+ JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest$A.class"),
+ JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest$B.class")
+ };
+ ImmutableSet<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.MergeDefaultMethodIntoClassTest",
+ "classmerging.MergeDefaultMethodIntoClassTest$B");
+ AndroidApp app =
+ AndroidApp.builder()
+ .addProgramFiles(programFiles)
+ .addLibraryFile(ToolHelper.getAndroidJar(AndroidApiLevel.O))
+ .build();
+
+ // Sanity check that there is actually an invoke-interface instruction in the input. We need
+ // to make sure that this invoke-interface instruction is translated to invoke-virtual after
+ // the classes A and B are merged.
+ DexInspector inputInspector = new DexInspector(app);
+ ClassSubject clazz = inputInspector.clazz("classmerging.MergeDefaultMethodIntoClassTest");
+ assertThat(clazz, isPresent());
+ MethodSubject method = clazz.method("void", "main", ImmutableList.of("java.lang.String[]"));
+ assertThat(method, isPresent());
+ assertThat(
+ method.getMethod().getCode().asJarCode().toString(),
+ containsString("INVOKEINTERFACE classmerging/MergeDefaultMethodIntoClassTest$A.f"));
+
+ runTestOnInput(main, app, preservedClassNames::contains, getProguardConfig(JAVA8_EXAMPLE_KEEP));
+ }
+
+ @Test
+ public void testNativeMethod() throws Exception {
+ String main = "classmerging.ClassWithNativeMethodTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ClassWithNativeMethodTest.class"),
+ CF_DIR.resolve("ClassWithNativeMethodTest$A.class"),
+ CF_DIR.resolve("ClassWithNativeMethodTest$B.class")
+ };
+ // Ensures that the class A with a native method has not been merged into its subclass B.
+ ImmutableSet<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.ClassWithNativeMethodTest",
+ "classmerging.ClassWithNativeMethodTest$A",
+ "classmerging.ClassWithNativeMethodTest$B");
+ runTest(main, programFiles, preservedClassNames::contains);
+ }
+
+ @Test
public void testNoIllegalClassAccess() throws Exception {
String main = "classmerging.SimpleInterfaceAccessTest";
Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 51a5262..3239191 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.OutputMode;
@@ -27,6 +28,7 @@
import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
import com.android.tools.r8.ir.optimize.classinliner.code.C;
import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -254,6 +256,51 @@
assertFalse(inspector.clazz(C.F.class).isPresent());
}
+ @Test
+ public void testInvalidatedRoot() throws Exception {
+ String prefix = "com.android.tools.r8.ir.optimize.classinliner.invalidroot.";
+
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.A.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.B.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.NeverReturnsNormally.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.InitNeverReturnsNormally.class)
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), InvalidRootsTestClass.class);
+
+ String javaOutput = runOnJava(InvalidRootsTestClass.class);
+ String artOutput = runOnArt(app, InvalidRootsTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
+
+ assertEquals(
+ Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+ collectTypes(clazz, "testExtraNeverReturnsNormally", "void"));
+
+ assertEquals(
+ Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+ collectTypes(clazz, "testDirectNeverReturnsNormally", "void"));
+
+ assertEquals(
+ Sets.newHashSet(prefix + "InvalidRootsTestClass$InitNeverReturnsNormally"),
+ collectTypes(clazz, "testInitNeverReturnsNormally", "void"));
+
+ assertTrue(inspector.clazz(InvalidRootsTestClass.NeverReturnsNormally.class).isPresent());
+ assertTrue(inspector.clazz(InvalidRootsTestClass.InitNeverReturnsNormally.class).isPresent());
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder",
+ "java.lang.RuntimeException"),
+ collectTypes(clazz, "testRootInvalidatesAfterInlining", "void"));
+
+ assertFalse(inspector.clazz(InvalidRootsTestClass.A.class).isPresent());
+ assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
+ }
+
private Set<String> collectTypes(
ClassSubject clazz, String methodName, String retValue, String... params) {
return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
new file mode 100644
index 0000000..26dc8cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.invalidroot;
+
+public class InvalidRootsTestClass {
+ private static int ID = 0;
+
+ private static String next() {
+ return Integer.toString(ID++);
+ }
+
+ public static void main(String[] args) {
+ InvalidRootsTestClass test = new InvalidRootsTestClass();
+ test.testExtraNeverReturnsNormally();
+ test.testDirectNeverReturnsNormally();
+ test.testInitNeverReturnsNormally();
+ test.testRootInvalidatesAfterInlining();
+ }
+
+ private synchronized void testExtraNeverReturnsNormally() {
+ testExtraNeverReturnsNormallyA();
+ testExtraNeverReturnsNormallyB();
+
+ try {
+ NeverReturnsNormally a = new NeverReturnsNormally();
+ neverReturnsNormallyExtra(next(), a);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testExtraNeverReturnsNormallyA() {
+ try {
+ neverReturnsNormallyExtra(next(), null);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testExtraNeverReturnsNormallyB() {
+ try {
+ neverReturnsNormallyExtra(next(), null);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testDirectNeverReturnsNormally() {
+ try {
+ NeverReturnsNormally a = new NeverReturnsNormally();
+ System.out.println(a.foo());
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testInitNeverReturnsNormally() {
+ try {
+ new InitNeverReturnsNormally();
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private void neverReturnsNormallyExtra(String prefix, NeverReturnsNormally a) {
+ throw new RuntimeException("neverReturnsNormallyExtra(" +
+ prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+ }
+
+ public static class NeverReturnsNormally {
+ public String foo() {
+ throw new RuntimeException("NeverReturnsNormally::foo(): " + next());
+ }
+ }
+
+ public static class InitNeverReturnsNormally {
+ public InitNeverReturnsNormally() {
+ throw new RuntimeException("InitNeverReturnsNormally::init(): " + next());
+ }
+
+ public String foo() {
+ return "InitNeverReturnsNormally::foo(): " + next();
+ }
+ }
+
+ private synchronized void testRootInvalidatesAfterInlining() {
+ A a = new A();
+ try {
+ notInlinedExtraMethod(next(), a);
+ System.out.println(new B().foo() + " " + next());
+ testRootInvalidatesAfterInliningA(a);
+ testRootInvalidatesAfterInliningB(a);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private void notInlinedExtraMethod(String prefix, A a) {
+ System.out.println("notInlinedExtraMethod(" +
+ prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+ if (a != null) {
+ throw new RuntimeException(
+ "notInlinedExtraMethod(" + prefix + ", " + a.foo() + "): " + next());
+ }
+ System.out.println("notInlinedExtraMethod(" + prefix + ", null): " + next());
+ }
+
+ private void testRootInvalidatesAfterInliningA(A a) {
+ notInlinedExtraMethod(next(), a);
+ }
+
+ private void testRootInvalidatesAfterInliningB(A a) {
+ notInlinedExtraMethod(next(), a);
+ }
+
+ public static class A {
+ public String foo() {
+ return "B::foo(" + next() + ")";
+ }
+ }
+
+ public static class B {
+ public String foo() {
+ return "B::foo(" + next() + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index ba04454..82381dd 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -131,6 +131,10 @@
return addMethod("public final", name, argumentTypes, returnType, lines);
}
+ public MethodSignature addVirtualMethod(String name, String returnType, String... lines) {
+ return addVirtualMethod(name, ImmutableList.of(), returnType, lines);
+ }
+
public MethodSignature addVirtualMethod(
String name,
List<String> argumentTypes,
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 6d16184..5cc07a4 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.GraphLense;
@@ -44,7 +45,7 @@
private DexApplication program;
DexItemFactory dexItemFactory;
- private AppInfoWithSubtyping appInfo;
+ private AppView<AppInfoWithSubtyping> appView;
NamingTestBase(
String test,
@@ -61,7 +62,7 @@
public void readApp() throws IOException, ExecutionException {
program = ToolHelper.buildApplication(ImmutableList.of(appFileName));
dexItemFactory = program.dexItemFactory;
- appInfo = new AppInfoWithSubtyping(program);
+ appView = new AppView<>(new AppInfoWithSubtyping(program), GraphLense.getIdentityLense());
}
NamingLens runMinifier(List<Path> configPaths)
@@ -73,12 +74,12 @@
ExecutorService executor = ThreadUtils.getExecutorService(1);
+ AppInfoWithSubtyping appInfo = appView.getAppInfo();
RootSet rootSet = new RootSetBuilder(appInfo, program, configuration.getRules(), options)
.run(executor);
if (options.proguardConfiguration.isAccessModificationAllowed()) {
- ClassAndMemberPublicizer.run(
- executor, timing, program, appInfo, rootSet, GraphLense.getIdentityLense());
+ ClassAndMemberPublicizer.run(executor, timing, program, appView, rootSet);
rootSet =
new RootSetBuilder(appInfo, program, configuration.getRules(), options).run(executor);
}
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index 3e46098..c929eaf 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -22,12 +22,12 @@
import com.google.common.collect.ImmutableList;
import java.util.Iterator;
import java.util.function.BiConsumer;
-import org.junit.Ignore;
import org.junit.Test;
public class NeverReturnsNormallyTest extends TestBase {
private void runTest(
- BiConsumer<DexInspector, CompilationMode> inspection, CompilationMode mode) throws Exception {
+ BiConsumer<DexInspector, CompilationMode> inspection,
+ boolean enableClassInliner, CompilationMode mode) throws Exception {
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
@@ -49,7 +49,8 @@
"-allowaccessmodification"
),
Origin.unknown());
- AndroidApp app = ToolHelper.runR8(builder.build());
+ AndroidApp app = ToolHelper.runR8(builder.build(),
+ opts -> opts.enableClassInlining = enableClassInliner);
inspection.accept(new DexInspector(app), mode);
// Run on Art to check generated code against verifier.
@@ -128,10 +129,11 @@
return instructions.next();
}
- @Ignore("b/110736241")
@Test
public void test() throws Exception {
- runTest(this::validate, CompilationMode.DEBUG);
- runTest(this::validate, CompilationMode.RELEASE);
+ runTest(this::validate, true, CompilationMode.DEBUG);
+ runTest(this::validate, true, CompilationMode.RELEASE);
+ runTest(this::validate, false, CompilationMode.DEBUG);
+ runTest(this::validate, false, CompilationMode.RELEASE);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index c176ff3..1379b95 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.DiagnosticsChecker.checkDiagnostics;
import static com.android.tools.r8.shaking.ProguardConfigurationSourceStrings.createConfigurationForTesting;
+import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -14,9 +16,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.hamcrest.core.StringContains.containsString;
-
-import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -25,10 +24,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.position.TextPosition;
-import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
@@ -153,7 +148,14 @@
public void resetAllowPartiallyImplementedOptions() {
handler = new KeepingDiagnosticHandler();
reporter = new Reporter(handler);
- parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, false);
+ parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, false, false);
+ }
+
+ @Before
+ public void resetAllowTestOptions() {
+ handler = new KeepingDiagnosticHandler();
+ reporter = new Reporter(handler);
+ parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, true, true);
}
@Test
@@ -694,7 +696,7 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(PACKAGE_OBFUSCATION_5);
parser.parse(path);
- checkDiagnostic(handler.warnings, path, 6, 1,
+ checkDiagnostics(handler.warnings, path, 6, 1,
"repackageclasses", "overrides", "flattenpackagehierarchy");
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
@@ -708,7 +710,7 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(PACKAGE_OBFUSCATION_6);
parser.parse(path);
- checkDiagnostic(handler.warnings, path, 6, 1,
+ checkDiagnostics(handler.warnings, path, 6, 1,
"repackageclasses", "overrides", "flattenpackagehierarchy");
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
@@ -735,7 +737,7 @@
parser.parse(path);
fail("Expect to fail due to the lack of file name.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 6, 14, "File name expected");
+ checkDiagnostics(handler.errors, path, 6, 14, "File name expected");
}
}
@@ -755,7 +757,7 @@
.parse(path);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 6, 10,"does-not-exist.flags");
+ checkDiagnostics(handler.errors, path, 6, 10,"does-not-exist.flags");
}
}
@@ -767,7 +769,7 @@
.parse(path);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 6,2, "does-not-exist.flags");
+ checkDiagnostics(handler.errors, path, 6,2, "does-not-exist.flags");
}
}
@@ -866,7 +868,7 @@
@Test
public void parseKeepdirectories() throws Exception {
ProguardConfigurationParser parser =
- new ProguardConfigurationParser(new DexItemFactory(), reporter, false);
+ new ProguardConfigurationParser(new DexItemFactory(), reporter, false, false);
parser.parse(Paths.get(KEEPDIRECTORIES));
verifyParserEndsCleanly();
}
@@ -930,7 +932,7 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES);
parser.parse(path);
- checkDiagnostic(handler.warnings, path, 7, 1,
+ checkDiagnostics(handler.warnings, path, 7, 1,
"Ignoring", "-optimizationpasses");
ProguardConfiguration config = parser.getConfig();
assertFalse(config.isOptimizing());
@@ -942,7 +944,7 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(OPTIMIZATION_PASSES);
parser.parse(path);
- checkDiagnostic(handler.warnings, path, 5, 1,
+ checkDiagnostics(handler.warnings, path, 5, 1,
"Ignoring", "-optimizationpasses");
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isOptimizing());
@@ -957,7 +959,7 @@
parser.parse(path);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 6, 1, "Missing n");
+ checkDiagnostics(handler.errors, path, 6, 1, "Missing n");
}
}
@@ -970,8 +972,8 @@
parser.parse(path);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 5, 1, "Unsupported option",
- "-skipnonpubliclibraryclasses");
+ checkDiagnostics(handler.errors, path, 5, 1,
+ "Unsupported option", "-skipnonpubliclibraryclasses");
}
}
@@ -1052,7 +1054,8 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 1, 1, "Unknown option", "-keepclassx");
+ checkDiagnostics(handler.errors, proguardConfig, 1, 1,
+ "Unknown option", "-keepclassx");
}
}
@@ -1325,7 +1328,7 @@
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " ,")));
fail("Expect to fail due to the lack of path filter.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, null, 1, option.length() + 2, "Path filter expected");
+ checkDiagnostics(handler.errors, null, 1, option.length() + 2, "Path filter expected");
}
}
}
@@ -1340,7 +1343,7 @@
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,,yyy")));
fail("Expect to fail due to the lack of path filter.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+ checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
}
}
}
@@ -1355,7 +1358,23 @@
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,")));
fail("Expect to fail due to the lack of path filter.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+ checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+ }
+ }
+ }
+
+ @Test
+ public void parse_testInlineOptions() {
+ List<String> options = ImmutableList.of(
+ "-neverinline", "-forceinline");
+ for (String option : options) {
+ try {
+ reset();
+ parser.parse(createConfigurationForTesting(ImmutableList.of(option + " class A { *; }")));
+ fail("Expect to fail due to testing option being turned off.");
+ } catch (AbortException e) {
+ assertEquals(2, handler.errors.size());
+ checkDiagnostics(handler.errors, 0, null, 1, 1, "Unknown option \"" + option + "\"");
}
}
}
@@ -1438,7 +1457,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+ checkDiagnostics(handler.errors, proguardConfig, 2, 13,
"Use of generics not allowed for java type");
}
verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
@@ -1456,7 +1475,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+ checkDiagnostics(handler.errors, proguardConfig, 2, 13,
"Use of generics not allowed for java type");
}
verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
@@ -1489,7 +1508,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- verifyParserEndsCleanly();
+ checkDiagnostics(handler.warnings, proguardConfig, 3, 7, "The field name \"id<<*>>\" is");
verifyWithProguard6(proguardConfig);
}
@@ -1508,7 +1527,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 4, 2,
+ checkDiagnostics(handler.errors, proguardConfig, 4, 2,
"Wildcard", "<4>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
@@ -1526,7 +1545,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+ checkDiagnostics(handler.errors, proguardConfig, 2, 13,
"Wildcard", "<0>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (0,");
@@ -1544,7 +1563,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 3, 1,
+ checkDiagnostics(handler.errors, proguardConfig, 3, 1,
"Wildcard", "<4>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
@@ -1562,7 +1581,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 3, 1,
+ checkDiagnostics(handler.errors, proguardConfig, 3, 1,
"Wildcard", "<2>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (2,");
@@ -1584,7 +1603,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 6, 2,
+ checkDiagnostics(handler.errors, proguardConfig, 6, 2,
"Wildcard", "<3>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (3,");
@@ -1602,7 +1621,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+ checkDiagnostics(handler.errors, proguardConfig, 1, 1,
"Expecting", "'-keep'", "after", "'-if'");
}
verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
@@ -1619,7 +1638,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+ checkDiagnostics(handler.errors, proguardConfig, 1, 1,
"Expecting", "'-keep'", "after", "'-if'");
}
verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
@@ -1649,7 +1668,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-assumenoexternalsideeffects");
}
@@ -1663,7 +1682,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-assumenoescapingparameters");
}
@@ -1687,7 +1706,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-assumenoexternalreturnvalues");
}
@@ -1721,7 +1740,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-addconfigurationdebugging");
}
@@ -1735,7 +1754,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- verifyParserEndsCleanly();
+ checkDiagnostics(handler.warnings, proguardConfig, 2, 5, "The field name \"<fields>\" is");
verifyWithProguard(proguardConfig);
}
@@ -1757,13 +1776,33 @@
verifyWithProguard(proguardConfig);
}
+ @Test
+ public void parse_regress110021323() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-keepclassmembernames class A {",
+ " <public methods>;",
+ " <public fields>;",
+ "}"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ assertEquals(4, handler.warnings.size());
+ checkDiagnostics(handler.warnings, 0, proguardConfig, 2, 3, "The type \"<public\" is");
+ checkDiagnostics(handler.warnings, 1, proguardConfig, 2, 11, "The field name \"methods>\" is");
+ checkDiagnostics(handler.warnings, 2, proguardConfig, 3, 3, "The type \"<public\" is");
+ checkDiagnostics(handler.warnings, 3, proguardConfig, 3, 11, "The field name \"fields>\" is");
+
+ verifyWithProguard(proguardConfig);
+ }
+
public void testNotSupported(String option) {
try {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(option)));
fail("Expect to fail due to unsupported option.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, null, 1, 1, "Option " + option + " currently not supported");
+ checkDiagnostics(handler.errors, null, 1, 1, "Option " + option + " currently not supported");
}
}
@@ -1787,31 +1826,6 @@
assertEquals(0, handler.errors.size());
}
- // TODO(sgjesse): Change to use DiagnosticsChecker.
- private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
- int columnStart, String... messageParts) {
- assertEquals(1, diagnostics.size());
- Diagnostic diagnostic = diagnostics.get(0);
- if (path != null) {
- assertEquals(path, ((PathOrigin) diagnostic.getOrigin()).getPath());
- } else {
- assertSame(Origin.unknown(), diagnostic.getOrigin());
- }
- TextPosition position;
- if (diagnostic.getPosition() instanceof TextRange) {
- position = ((TextRange) diagnostic.getPosition()).getStart();
- } else {
- position = ((TextPosition) diagnostic.getPosition());
- }
- assertEquals(lineStart, position.getLine());
- assertEquals(columnStart, position.getColumn());
- for (String part : messageParts) {
- assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
- diagnostic.getDiagnosticMessage().contains(part));
- }
- return diagnostic;
- }
-
private void verifyWithProguard(Path proguardConfig) throws Exception {
if (isRunProguard()) {
// Add a keep rule for the test class as Proguard will fail if the resulting output jar is
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 5845dd3..e543d46 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -86,18 +86,24 @@
" public int method();",
"}"
), this::defaultMethodKept);
- runTest(ImmutableList.of(
- "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
- " <methods>;",
- "}"
- ), this::defaultMethodNotKept);
- runTest(ImmutableList.of(
- "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
- " <methods>;",
- "}",
- "-keep class " + TestClass.class.getCanonicalName() + "{",
- " public void useInterfaceMethod();",
- "}"
- ), this::defaultMethodAbstract);
+ runTest(
+ ImmutableList.of(
+ "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
+ " <methods>;",
+ "}",
+ // Prevent InterfaceWithDefaultMethods from being merged into ClassImplementingInterface
+ "-keep class " + InterfaceWithDefaultMethods.class.getCanonicalName()),
+ this::defaultMethodNotKept);
+ runTest(
+ ImmutableList.of(
+ "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
+ " <methods>;",
+ "}",
+ "-keep class " + TestClass.class.getCanonicalName() + "{",
+ " public void useInterfaceMethod();",
+ "}",
+ // Prevent InterfaceWithDefaultMethods from being merged into ClassImplementingInterface
+ "-keep class " + InterfaceWithDefaultMethods.class.getCanonicalName()),
+ this::defaultMethodAbstract);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
index 10c64e3..693f02d 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -99,6 +99,7 @@
throws Exception {
AndroidApp app = readClassesAndAndriodJar(programClasses);
R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
+ ToolHelper.allowTestProguardOptions(builder);
builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
return ToolHelper.runR8(builder.build(), configure);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
index 14391ee..22f9ef6 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
@@ -33,13 +33,6 @@
}
}
-class B {
- // Depending on inlining option, this method is kept to make inlining of A.a() infeasible.
- void x() {
- System.out.print("" + A.a() + A.a() + A.a() + A.a() + A.a() + A.a() + A.a() + A.a());
- }
-}
-
class D {
}
@@ -52,7 +45,7 @@
@RunWith(Parameterized.class)
public class IfRuleWithInlining extends ProguardCompatabilityTestBase {
private final static List<Class> CLASSES = ImmutableList.of(
- A.class, B.class, D.class, Main.class);
+ A.class, D.class, Main.class);
private final Shrinker shrinker;
private final boolean inlineMethod;
@@ -90,11 +83,8 @@
List<String> config = ImmutableList.of(
"-keep class **.Main { public static void main(java.lang.String[]); }",
inlineMethod
- ? "-alwaysinline class **.A { int a(); }"
- : "-keep class **.B { *; }",
- inlineMethod
- ? "-checkdiscard class **.A { int a(); }"
- : "",
+ ? "-forceinline class **.A { int a(); }"
+ : "-neverinline class **.A { int a(); }",
"-if class **.A { static int a(); }",
"-keep class **.D",
"-dontobfuscate"
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/A.java b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
new file mode 100644
index 0000000..af05562
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.testrules;
+
+public class A {
+
+ public static int m(int a, int b) {
+ int r = a + b;
+ System.out.println(a + " + " + b + " = " + r);
+ return r;
+ }
+
+ public static int method() {
+ return m(m(m(1, 2), m(3, 4)), m(m(5, 6), m(7, 8)));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/B.java b/src/test/java/com/android/tools/r8/shaking/testrules/B.java
new file mode 100644
index 0000000..4d1f085
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/B.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.testrules;
+
+public class B {
+
+ public int m(int a, int b) {
+ int r = a + b;
+ System.out.println(a + " + " + b + " = " + r);
+ return r;
+ }
+ public int method() {
+ return m(m(m(1, 2), m(3, 4)), m(m(5, 6), m(7, 8)));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/C.java b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
new file mode 100644
index 0000000..5ee4d53
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.testrules;
+
+public class C {
+
+ private static int i;
+
+ public static int x() {
+ return i;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
new file mode 100644
index 0000000..e4be101
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.testrules;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+
+public class ForceInlineTest extends TestBase {
+
+ private DexInspector runTest(List<String> proguardConfiguration) throws Exception {
+ R8Command.Builder builder =
+ ToolHelper.prepareR8CommandBuilder(readClasses(Main.class, A.class, B.class, C.class));
+ ToolHelper.allowTestProguardOptions(builder);
+ builder.addProguardConfiguration(proguardConfiguration, Origin.unknown());
+ return new DexInspector(ToolHelper.runR8(builder.build()));
+ }
+
+ @Test
+ public void testDefaultInlining() throws Exception {
+ DexInspector inspector = runTest(ImmutableList.of(
+ "-keep class **.Main { *; }",
+ "-dontobfuscate"
+ ));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ ClassSubject classB = inspector.clazz(B.class);
+ ClassSubject classC = inspector.clazz(C.class);
+ ClassSubject classMain = inspector.clazz(Main.class);
+ assertThat(classA, isPresent());
+ assertThat(classB, isPresent());
+ assertThat(classC, isPresent());
+ assertThat(classMain, isPresent());
+
+ // By default A.m *will not* be inlined (called several times and not small).
+ assertThat(classA.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+ // By default A.method *will* be inlined (called only once).
+ assertThat(classA.method("int", "method", ImmutableList.of()), not(isPresent()));
+ // By default B.m *will not* be inlined (called several times and not small).
+ assertThat(classB.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+ // By default B.method *will* be inlined (called only once).
+ assertThat(classB.method("int", "method", ImmutableList.of()), not(isPresent()));
+ }
+
+ @Test
+ public void testNeverInline() throws Exception {
+ DexInspector inspector = runTest(ImmutableList.of(
+ "-neverinline class **.A { method(); }",
+ "-neverinline class **.B { method(); }",
+ "-keep class **.Main { *; }",
+ "-dontobfuscate"
+ ));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ ClassSubject classB = inspector.clazz(B.class);
+ ClassSubject classC = inspector.clazz(C.class);
+ ClassSubject classMain = inspector.clazz(Main.class);
+ assertThat(classA, isPresent());
+ assertThat(classB, isPresent());
+ assertThat(classC, isPresent());
+ assertThat(classMain, isPresent());
+
+ // Compared to the default method is no longer inlined.
+ assertThat(classA.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+ assertThat(classA.method("int", "method", ImmutableList.of()), isPresent());
+ assertThat(classB.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+ assertThat(classB.method("int", "method", ImmutableList.of()), isPresent());
+ }
+
+ @Test
+ public void testForceInline() throws Exception {
+ DexInspector inspector = runTest(ImmutableList.of(
+ "-forceinline class **.A { int m(int, int); }",
+ "-forceinline class **.B { int m(int, int); }",
+ "-keep class **.Main { *; }",
+ "-dontobfuscate"
+ ));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ ClassSubject classB = inspector.clazz(B.class);
+ ClassSubject classC = inspector.clazz(C.class);
+ ClassSubject classMain = inspector.clazz(Main.class);
+
+ // Compared to the default m is now inlined and method still is, so classes A and B are gone.
+ assertThat(classA, not(isPresent()));
+ assertThat(classB, not(isPresent()));
+ assertThat(classC, isPresent());
+ assertThat(classMain, isPresent());
+ }
+
+ @Test
+ public void testForceInlineFails() throws Exception {
+ try {
+ DexInspector inspector = runTest(ImmutableList.of(
+ "-forceinline class **.A { int x(); }",
+ "-keep class **.Main { *; }",
+ "-dontobfuscate"
+ ));
+ fail("Force inline of non-inlinable method succeeded");
+ } catch (Throwable t) {
+ // Ignore assertion error.
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/Main.java b/src/test/java/com/android/tools/r8/shaking/testrules/Main.java
new file mode 100644
index 0000000..d326216
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/Main.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.testrules;
+
+public class Main {
+
+ public static void main(String[] args) {
+ System.out.println(A.method());
+ System.out.println(new B().method());
+ System.out.println(C.x());
+ }
+}
diff --git a/src/test/sampleApks/split/split.spec b/src/test/sampleApks/split/split.spec
index a06e259..ed40b98 100644
--- a/src/test/sampleApks/split/split.spec
+++ b/src/test/sampleApks/split/split.spec
@@ -1 +1,2 @@
com.android.tools.r8.sample.split.SplitClass:split
+com.android.tools.r8.sample.split.SplitInheritBase:split
diff --git a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/BaseClass.java b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/BaseClass.java
new file mode 100644
index 0000000..eef119c
--- /dev/null
+++ b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/BaseClass.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.sample.split;
+
+
+public class BaseClass {
+
+ int initialValue;
+
+ public BaseClass(int initialValue) {
+ this.initialValue = initialValue;
+ }
+
+ public int calculate(int x) {
+ int result = 2;
+ for (int i = 0; i < 42; i++) {
+ result += initialValue + x;
+ }
+ return result;
+ }
+
+ public int largeMethod(int x, int y) {
+ int a = x + y;
+ int b;
+ int c;
+ double d;
+ String s;
+ if (a < 22) {
+ b = a * 42 / y;
+ c = b - 80;
+ d = a + b + c * y - x * a;
+ s = "foobar";
+ } else if (a < 42) {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "foo";
+ } else {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "bar";
+ }
+ if (s.equals("bar")) {
+ d = 49;
+ s += b;
+ } else {
+ b = 63;
+ s = "barbar" + b;
+ }
+
+ if (a < 22) {
+ b = a * 42 / y;
+ c = b - 80;
+ d = a + b + c * y - x * a;
+ s = "foobar";
+ } else if (a < 42) {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "foo";
+ } else {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "bar";
+ }
+ if (s.equals("bar")) {
+ d = 49;
+ s += b;
+ } else {
+ b = 63;
+ s = "barbar" + b;
+ }
+
+ if (a < 22) {
+ b = a * 42 / y;
+ c = b - 80;
+ d = a + b + c * y - x * a;
+ s = "foobar";
+ } else if (a < 42) {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "foo";
+ } else {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "bar";
+ }
+ if (s.equals("bar")) {
+ d = 49;
+ s += b;
+ } else {
+ b = 63;
+ s = "barbar" + b;
+ }
+
+ return a + b - c * x;
+ }
+}
diff --git a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/R8Activity.java b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/R8Activity.java
index a80cf55..307cac9 100644
--- a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/R8Activity.java
+++ b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/R8Activity.java
@@ -8,33 +8,201 @@
import android.os.Bundle;
import com.android.tools.r8.sample.split.R;
import com.android.tools.r8.sample.split.SplitClass;
+import java.util.ArrayList;
+import java.util.List;
+
public class R8Activity extends Activity {
+ // Enables easy splitting of iterations to better see effect of jit in later versions of art
+ public static final int ITERATIONS = 100000;
+ public static final int SPLITS = 1;
+
private int res = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(android.R.style.Theme_Light);
setContentView(R.layout.main);
- // Currently this is split up into 100 iterations to be able to better see
- // the impact of the jit on later versions of art.
long total = 0;
- for (int i = 0; i < 100; i++) {
+ for (int i = 0; i < SPLITS; i++) {
+ total += benchmarkCallBaseline();
+ }
+ System.out.println("CallBaseline Total: " + total);
+
+ total = 0;
+ for (int i = 0; i < SPLITS; i++) {
total += benchmarkCall();
}
- System.out.println("Total: " + total);
+ System.out.println("Call Total: " + total);
+
+ total = 0;
+ for (int i = 0; i < SPLITS; i++) {
+ total += benchmarkCallLocal();
+ }
+ System.out.println("CallLocal Total: " + total);
+
+ total = 0;
+ for (int i = 0; i < SPLITS; i++) {
+ total += benchmarkSplitCallback();
+ }
+ System.out.println("Callback Total: " + total);
+
+ total = 0;
+ for (int i = 0; i < SPLITS; i++) {
+ total += benchmarkConstructor();
+ }
+ System.out.println("Constructor Total: " + total);
+
+ total = 0;
+ for (int i = 0; i < SPLITS; i++) {
+ total += benchmarkConstructorLocal();
+ }
+ System.out.println("ConstructorLocal Total: " + total);
+
+ total = 0;
+ for (int i = 0; i < SPLITS; i++) {
+ total += benchmarkInheritanceConstructor();
+ }
+ System.out.println("InheritanceConstructor Total: " + total);
+
+ total = 0;
+ for (int i = 0; i < SPLITS; i++) {
+ total += benchmarkLargeMethodCall();
+ }
+ System.out.println("LargeMethodCall Total: " + total);
+
+ total = 0;
+ for (int i = 0; i < SPLITS; i++) {
+ total += benchmarkSplitCallbackLong();
+ }
+ System.out.println("CallbackLarge Total: " + total);
+
+ total = 0;
+ for (int i = 0; i < SPLITS; i++) {
+ total += benchmarkLargeMethodCallLocally();
+ }
+ System.out.println("LargeMethodCallLocal Total: " + total);
+
}
- public long benchmarkCall() {
+ private long benchmarkCall() {
SplitClass split = new SplitClass(3);
long start = System.nanoTime();
- for (int i = 0; i < 1000; i++) {
+ for (int i = 0; i < ITERATIONS / SPLITS; i++) {
// Ensure no dead code elimination.
res = split.calculate(i);
}
long finish = System.nanoTime();
- long timeElapsed = finish - start;
- System.out.println("Took: " + timeElapsed);
+ long timeElapsed = (finish - start) / 1000;
return timeElapsed;
}
+
+ private long benchmarkCallLocal() {
+ long start = System.nanoTime();
+ for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+ // Ensure no dead code elimination.
+ res = calculate(i);
+ }
+ long finish = System.nanoTime();
+ long timeElapsed = (finish - start) / 1000;
+ return timeElapsed;
+ }
+
+ private long benchmarkCallBaseline() {
+ SplitClass split = new SplitClass(3);
+ long start = System.nanoTime();
+ for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+ int result = 2;
+ for (int j = 0; j < 42; j++) {
+ result += result + i;
+ }
+ res = result;
+ }
+ long finish = System.nanoTime();
+ long timeElapsed = (finish - start) / 1000;
+ return timeElapsed;
+ }
+
+ private long benchmarkInheritanceConstructor() {
+ List<SplitInheritBase> instances = new ArrayList<SplitInheritBase>();
+ long start = System.nanoTime();
+ for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+ instances.add(new SplitInheritBase(i));
+ }
+ long finish = System.nanoTime();
+ long timeElapsed = (finish - start) / 1000;
+ return timeElapsed;
+ }
+
+ private long benchmarkConstructor() {
+ List<SplitClass> instances = new ArrayList<SplitClass>();
+ long start = System.nanoTime();
+ for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+ instances.add(new SplitClass(i));
+ }
+ long finish = System.nanoTime();
+ long timeElapsed = (finish - start) / 1000;
+ return timeElapsed;
+ }
+
+ private long benchmarkConstructorLocal() {
+ List<BaseClass> instances = new ArrayList<BaseClass>();
+ long start = System.nanoTime();
+ for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+ instances.add(new BaseClass(i));
+ }
+ long finish = System.nanoTime();
+ long timeElapsed = (finish - start) / 1000;
+ return timeElapsed;
+ }
+
+ private long benchmarkLargeMethodCall() {
+ SplitClass split = new SplitClass(3);
+ long start = System.nanoTime();
+ for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+ // Ensure no dead code elimination.
+ res = split.largeMethod(i, i + 1);
+ }
+ long finish = System.nanoTime();
+ long timeElapsed = (finish - start) / 1000;
+ return timeElapsed;
+ }
+
+ private long benchmarkSplitCallback() {
+ SplitClass split = new SplitClass(3);
+ long start = System.nanoTime();
+ res = split.callBase();
+ long finish = System.nanoTime();
+ long timeElapsed = (finish - start) / 1000;
+ return timeElapsed;
+ }
+
+ private long benchmarkSplitCallbackLong() {
+ SplitClass split = new SplitClass(3);
+ long start = System.nanoTime();
+ res = split.callBaseLarge();
+ long finish = System.nanoTime();
+ long timeElapsed = (finish - start) / 1000;
+ return timeElapsed;
+ }
+
+ private long benchmarkLargeMethodCallLocally() {
+ BaseClass base = new BaseClass(3);
+ long start = System.nanoTime();
+ for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+ // Ensure no dead code elimination.
+ res = base.largeMethod(i, i + 1);
+ }
+ long finish = System.nanoTime();
+ long timeElapsed = (finish - start) / 1000;
+ return timeElapsed;
+ }
+
+ public int calculate(int x) {
+ int result = 2;
+ for (int i = 0; i < 42; i++) {
+ result += res + x;
+ }
+ return result;
+ }
}
diff --git a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitClass.java b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitClass.java
index 9b6990d..b493326 100644
--- a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitClass.java
+++ b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitClass.java
@@ -4,13 +4,17 @@
package com.android.tools.r8.sample.split;
+import com.android.tools.r8.sample.split.R8Activity;
+
public class SplitClass {
- final int initialValue;
+ int initialValue;
public SplitClass(int initialValue) {
this.initialValue = initialValue;
}
+
+
public int calculate(int x) {
int result = 2;
for (int i = 0; i < 42; i++) {
@@ -18,4 +22,104 @@
}
return result;
}
+
+ public int largeMethod(int x, int y) {
+ int a = x + y;
+ int b;
+ int c;
+ double d;
+ String s;
+ if (a < 22) {
+ b = a * 42 / y;
+ c = b - 80;
+ d = a + b + c * y - x * a;
+ s = "foobar";
+ } else if (a < 42) {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "foo";
+ } else {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "bar";
+ }
+ if (s.equals("bar")) {
+ d = 49;
+ s += b;
+ } else {
+ b = 63;
+ s = "barbar" + b;
+ }
+
+ if (a < 22) {
+ b = a * 42 / y;
+ c = b - 80;
+ d = a + b + c * y - x * a;
+ s = "foobar";
+ } else if (a < 42) {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "foo";
+ } else {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "bar";
+ }
+ if (s.equals("bar")) {
+ d = 49;
+ s += b;
+ } else {
+ b = 63;
+ s = "barbar" + b;
+ }
+
+ if (a < 22) {
+ b = a * 42 / y;
+ c = b - 80;
+ d = a + b + c * y - x * a;
+ s = "foobar";
+ } else if (a < 42) {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "foo";
+ } else {
+ b = x * 42 / y;
+ c = b - 850;
+ d = a + b + c * x - x * a;
+ s = "bar";
+ }
+ if (s.equals("bar")) {
+ d = 49;
+ s += b;
+ } else {
+ b = 63;
+ s = "barbar" + b;
+ }
+
+ return a + b - c * x;
+ }
+
+ public int callBase() {
+ BaseClass base = new BaseClass(initialValue);
+ for (int i = 0; i < R8Activity.ITERATIONS / R8Activity.SPLITS; i++) {
+ // Ensure no dead code elimination.
+ initialValue = base.calculate(i);
+ }
+ return initialValue;
+ }
+
+ public int callBaseLarge() {
+ BaseClass base = new BaseClass(initialValue);
+ for (int i = 0; i < R8Activity.ITERATIONS / R8Activity.SPLITS; i++) {
+ // Ensure no dead code elimination.
+ initialValue = base.largeMethod(i, i + 1);
+ }
+ return initialValue;
+ }
+
}
diff --git a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitInheritBase.java b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitInheritBase.java
new file mode 100644
index 0000000..6c5ed1e
--- /dev/null
+++ b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitInheritBase.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.sample.split;
+
+public class SplitInheritBase extends BaseClass {
+
+ public SplitInheritBase(int initialValue) {
+ super(initialValue);
+ initialValue = calculate(initialValue);
+ }
+
+ public int calculate(int x) {
+ return super.calculate(x) * 2;
+ }
+}
diff --git a/tools/build_sample_apk.py b/tools/build_sample_apk.py
index 90e34ed..b74356b 100755
--- a/tools/build_sample_apk.py
+++ b/tools/build_sample_apk.py
@@ -26,7 +26,7 @@
DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
PACKAGE_PREFIX = 'com.android.tools.r8.sample'
STANDARD_ACTIVITY = "R8Activity"
-BENCHMARK_ITERATIONS = 100
+BENCHMARK_ITERATIONS = 30
SAMPLE_APKS = [
'simple',
@@ -161,11 +161,15 @@
utils.PrintCmd(command)
subprocess.check_call(command)
-def run_adb(args):
+def run_adb(args, ignore_exit=False):
command = ['adb']
command.extend(args)
utils.PrintCmd(command)
- subprocess.check_call(command)
+ # On M adb install-multiple exits 1 but succeed in installing.
+ if ignore_exit:
+ subprocess.call(command)
+ else:
+ subprocess.check_call(command)
def adb_install(apks):
args = [
@@ -173,7 +177,7 @@
'-r',
'-d']
args.extend(apks)
- run_adb(args)
+ run_adb(args, ignore_exit=True)
def create_temp_apk(app, prefix):
temp_apk_path = os.path.join(get_bin_path(app), '%s.ap_' % app)
@@ -192,7 +196,7 @@
run_adb(args)
def start_logcat():
- return subprocess.Popen(['adb', 'logcat'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return subprocess.Popen(['adb', 'logcat'], bufsize=1024*1024, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def start(app):
args = ['shell', 'am', 'start', '-n', get_qualified_activity(app)]
@@ -211,34 +215,35 @@
return lines
def store_or_print_benchmarks(lines, output):
- single_runs = []
- total_time = None
- # We assume that the individual runs are prefixed with 'Took: ' and the total time is
- # prefixed with 'Total: '. The logcat lines looks like:
- # 06-28 12:22:00.991 13698 13698 I System.out: Took: 61614
+ results = {}
+ overall_total = 0
+ # We assume that the total times are
+ # prefixed with 'NAME Total: '. The logcat lines looks like:
+ # 06-28 12:22:00.991 13698 13698 I System.out: Call Total: 61614
for l in lines:
- if 'Took: ' in l:
- timing = l.split('Took: ')[1]
- single_runs.append(timing)
if 'Total: ' in l:
- timing = l.split('Total: ')[1]
- total_time = timing
- assert len(single_runs) > 0
- assert total_time
- if not output:
- print 'Individual timings: \n%s' % ''.join(single_runs)
- print 'Total time: \n%s' % total_time
- return
+ split = l.split('Total: ')
+ time = split[1]
+ name = split[0].split()[-1]
+ overall_total += int(time)
+ print '%s: %s' % (name, time)
+ results[name] = time
+ print 'Total: %s' % overall_total
+ if not output:
+ return overall_total
+ results['total'] = str(overall_total)
output_dir = os.path.join(output, str(uuid.uuid4()))
os.makedirs(output_dir)
- single_run_file = os.path.join(output_dir, 'single_runs')
- with open(single_run_file, 'w') as f:
- f.writelines(single_runs)
- total_file = os.path.join(output_dir, 'total')
- with open(total_file, 'w') as f:
- f.write(total_time)
- print 'Result stored in %s and %s' % (single_run_file, total_file)
+ written_files = []
+ for name, time in results.iteritems():
+ total_file = os.path.join(output_dir, name)
+ written_files.append(total_file)
+ with open(total_file, 'w') as f:
+ f.write(time)
+
+ print 'Result stored in: \n%s' % ('\n'.join(written_files))
+ return overall_total
def benchmark(app, output_dir):
# Ensure app is not running
@@ -248,9 +253,14 @@
start(app)
# We could do better here by continiously parsing the logcat for a marker, but
# this works nicely with the current setup.
- time.sleep(3)
+ time.sleep(8)
kill(app)
- store_or_print_benchmarks(stop_logcat(logcat), output_dir)
+ return float(store_or_print_benchmarks(stop_logcat(logcat), output_dir))
+
+def ensure_no_logcat():
+ output = subprocess.check_output(['ps', 'aux'])
+ if 'adb logcat' in output:
+ raise Exception('You have adb logcat running, please close it and rerun')
def Main():
(options, args) = parse_options()
@@ -283,11 +293,14 @@
apks.append(split_apk_path)
print('Generated apks available at: %s' % ' '.join(apks))
- if options.install:
+ if options.install or options.benchmark:
adb_install(apks)
+ grand_total = 0
if options.benchmark:
+ ensure_no_logcat()
for _ in range(BENCHMARK_ITERATIONS):
- benchmark(options.app, options.benchmark_output_dir)
+ grand_total += benchmark(options.app, options.benchmark_output_dir)
+ print 'Combined average: %s' % (grand_total/BENCHMARK_ITERATIONS)
if __name__ == '__main__':
sys.exit(Main())
diff --git a/tools/track_memory.sh b/tools/track_memory.sh
index c0b4716..4629d4b 100755
--- a/tools/track_memory.sh
+++ b/tools/track_memory.sh
@@ -16,7 +16,7 @@
function Exit {
kill $lid
- exit 0
+ exit $code
}
function Kill {
@@ -41,3 +41,4 @@
trap "Exit" EXIT
trap "Kill" SIGINT
wait $pid
+code=$?