Merge commit '9043b7c27b5c987737aa411f9ac562fda3254197' into dev-release
diff --git a/build.gradle b/build.gradle
index 77b5865..5c95cd6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -310,6 +310,7 @@
"android_jar/lib-v27",
"android_jar/lib-v28",
"android_jar/lib-v29",
+ "android_jar/lib-v30",
"core-lambda-stubs",
"dart-sdk",
"ddmlib",
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 615ca13..3c132e0 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -28,11 +28,13 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.PrefixRewritingNamingLens;
import com.android.tools.r8.origin.CommandLineOrigin;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
@@ -40,7 +42,10 @@
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -128,7 +133,7 @@
/**
* Command-line entry to D8.
*
- * See {@link D8Command#USAGE_MESSAGE} or run {@code d8 --help} for usage information.
+ * <p>See {@link D8Command#USAGE_MESSAGE} or run {@code d8 --help} for usage information.
*/
public static void main(String[] args) {
if (args.length == 0) {
@@ -215,10 +220,18 @@
// Preserve markers from input dex code and add a marker with the current version
// if there were class file inputs.
boolean hasClassResources = false;
+ boolean hasDexResources = false;
for (DexProgramClass dexProgramClass : app.classes()) {
if (dexProgramClass.originatesFromClassResource()) {
hasClassResources = true;
- break;
+ if (hasDexResources) {
+ break;
+ }
+ } else if (dexProgramClass.originatesFromDexResource()) {
+ hasDexResources = true;
+ if (hasClassResources) {
+ break;
+ }
}
}
Marker marker = options.getMarker(Tool.D8);
@@ -239,14 +252,30 @@
null)
.write(options.getClassFileConsumer(), executor);
} else {
+ NamingLens namingLens;
+ DexApplication finalApp = app;
+ if (!hasDexResources || !hasClassResources || !appView.rewritePrefix.isRewriting()) {
+ // All inputs are either dex or cf, or there is nothing to rewrite.
+ namingLens =
+ hasDexResources
+ ? NamingLens.getIdentityLens()
+ : PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
+ } else {
+ // There are both cf and dex inputs in the program, and rewriting is required for
+ // desugared library only on cf inputs. We cannot easily rewrite part of the program
+ // without iterating again the IR. We fall-back to writing one app with rewriting and
+ // merging it with the other app in rewriteNonDexInputs.
+ finalApp = rewriteNonDexInputs(appView, inputApp, options, executor, timing, app);
+ namingLens = NamingLens.getIdentityLens();
+ }
new ApplicationWriter(
- app,
- null,
+ finalApp,
+ appView,
options,
marker == null ? null : ImmutableList.copyOf(markers),
GraphLense.getIdentityLense(),
InitClassLens.getDefault(),
- PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
+ namingLens,
null)
.write(executor);
}
@@ -262,6 +291,57 @@
}
}
+ private static DexApplication rewriteNonDexInputs(
+ AppView<?> appView,
+ AndroidApp inputApp,
+ InternalOptions options,
+ ExecutorService executor,
+ Timing timing,
+ DexApplication app)
+ throws IOException, ExecutionException {
+ // TODO(b/154575955): Remove the naming lens in D8.
+ appView
+ .options()
+ .reporter
+ .warning(
+ new StringDiagnostic(
+ "The compilation is slowed down due to a mix of class file and dex file inputs in"
+ + " the context of desugared library. This can be fixed by pre-compiling to"
+ + " dex the class file inputs and dex merging only dex files."));
+ List<DexProgramClass> dexProgramClasses = new ArrayList<>();
+ List<DexProgramClass> nonDexProgramClasses = new ArrayList<>();
+ for (DexProgramClass aClass : app.classes()) {
+ if (aClass.originatesFromDexResource()) {
+ dexProgramClasses.add(aClass);
+ } else {
+ nonDexProgramClasses.add(aClass);
+ }
+ }
+ DexApplication cfApp = app.builder().replaceProgramClasses(nonDexProgramClasses).build();
+ ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
+ new ApplicationWriter(
+ cfApp,
+ null,
+ options,
+ null,
+ GraphLense.getIdentityLense(),
+ InitClassLens.getDefault(),
+ PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
+ null,
+ convertedCfFiles)
+ .write(executor);
+ AndroidApp.Builder builder = AndroidApp.builder(inputApp);
+ builder.getProgramResourceProviders().clear();
+ builder.addProgramResourceProvider(convertedCfFiles);
+ AndroidApp newAndroidApp = builder.build();
+ DexApplication newApp = new ApplicationReader(newAndroidApp, options, timing).read(executor);
+ DexApplication.Builder<?> finalDexApp = newApp.builder();
+ for (DexProgramClass dexProgramClass : dexProgramClasses) {
+ finalDexApp.addProgramClass(dexProgramClass);
+ }
+ return finalDexApp.build();
+ }
+
static DexApplication optimize(
DexApplication application,
AppInfo appInfo,
@@ -278,13 +358,35 @@
if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
System.out.print(printer.toString());
} else {
- try (OutputStreamWriter writer = new OutputStreamWriter(
- new FileOutputStream(options.printCfgFile),
- StandardCharsets.UTF_8)) {
+ try (OutputStreamWriter writer =
+ new OutputStreamWriter(
+ new FileOutputStream(options.printCfgFile), StandardCharsets.UTF_8)) {
writer.write(printer.toString());
}
}
}
return application;
}
+
+ static class ConvertedCfFiles implements DexIndexedConsumer, ProgramResourceProvider {
+
+ private final List<ProgramResource> resources = new ArrayList<>();
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ // TODO(b/154106502): Map Origin information.
+ resources.add(
+ ProgramResource.fromBytes(
+ Origin.unknown(), ProgramResource.Kind.DEX, data.copyByteData(), descriptors));
+ }
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return resources;
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+ }
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 39ed0b5..17c6a0a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -61,6 +61,7 @@
import com.android.tools.r8.naming.SeedMapper;
import com.android.tools.r8.naming.SourceFileRewriter;
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
+import com.android.tools.r8.optimize.BridgeHoisting;
import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.VisibilityBridgeRemover;
@@ -99,6 +100,7 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
@@ -290,8 +292,8 @@
List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
timing.begin("Strip unused code");
Set<DexType> classesToRetainInnerClassAttributeFor = null;
+ Set<DexType> missingClasses = appView.appInfo().getMissingClasses();
try {
- Set<DexType> missingClasses = appView.appInfo().getMissingClasses();
missingClasses = filterMissingClasses(
missingClasses, options.getProguardConfiguration().getDontWarnPatterns());
if (!missingClasses.isEmpty()) {
@@ -348,6 +350,9 @@
assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
assert appView.rootSet().verifyKeptItemsAreKept(appView.appInfo().app(), appView.appInfo());
+ missingClasses =
+ Sets.union(missingClasses, appViewWithLiveness.appInfo().getMissingTypes());
+
appView.rootSet().checkAllRulesAreUsed(options);
if (options.proguardSeedsConsumer != null) {
@@ -636,7 +641,8 @@
}
}
- Enqueuer enqueuer = EnqueuerFactory.createForFinalTreeShaking(appView, keptGraphConsumer);
+ Enqueuer enqueuer =
+ EnqueuerFactory.createForFinalTreeShaking(appView, keptGraphConsumer, missingClasses);
appView.setAppInfo(
enqueuer
.traceApplication(
@@ -681,6 +687,8 @@
pruner.getMethodsToKeepForConfigurationDebugging()));
appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
+ new BridgeHoisting(appViewWithLiveness).run();
+
// TODO(b/130721661): Enable this assert.
// assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
@@ -725,7 +733,7 @@
// TODO(b/112437944): Avoid iterating the entire application to post-process every
// dynamicMethod() method.
appView.withGeneratedMessageLiteShrinker(
- shrinker -> shrinker.postOptimizeDynamicMethods(converter, timing));
+ shrinker -> shrinker.postOptimizeDynamicMethods(converter, executorService, timing));
// If proto shrinking is enabled, we need to post-process every
// findLiteExtensionByNumber() method. This ensures that there are no references to dead
@@ -733,7 +741,9 @@
// TODO(b/112437944): Avoid iterating the entire application to post-process every
// findLiteExtensionByNumber() method.
appView.withGeneratedExtensionRegistryShrinker(
- shrinker -> shrinker.postOptimizeGeneratedExtensionRegistry(converter, timing));
+ shrinker ->
+ shrinker.postOptimizeGeneratedExtensionRegistry(
+ converter, executorService, timing));
}
}
@@ -858,12 +868,21 @@
appView.dexItemFactory(), OptimizationFeedbackSimple.getInstance()));
}
- return appView.setAppInfo(
- enqueuer.traceApplication(
- appView.rootSet(),
- options.getProguardConfiguration().getDontWarnPatterns(),
- executorService,
- timing));
+ AppView<AppInfoWithLiveness> appViewWithLiveness =
+ appView.setAppInfo(
+ enqueuer.traceApplication(
+ appView.rootSet(),
+ options.getProguardConfiguration().getDontWarnPatterns(),
+ executorService,
+ timing));
+ if (InternalOptions.assertionsEnabled()) {
+ // Register the dead proto types. These are needed to verify that no new missing types are
+ // reported and that no dead proto types are referenced in the generated application.
+ appViewWithLiveness.withProtoShrinker(
+ shrinker ->
+ shrinker.setDeadProtoTypes(appViewWithLiveness.appInfo().getDeadProtoTypes()));
+ }
+ return appViewWithLiveness;
}
static void processWhyAreYouKeepingAndCheckDiscarded(
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index c4c318c..79b4362 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -188,7 +188,7 @@
this.programConsumer = consumer;
}
- private Iterable<VirtualFile> distribute(ExecutorService executorService)
+ private List<VirtualFile> distribute(ExecutorService executorService)
throws ExecutionException, IOException {
// Distribute classes into dex files.
VirtualFile.Distributor distributor;
@@ -203,9 +203,7 @@
} else {
distributor = new VirtualFile.FillFilesDistributor(this, options, executorService);
}
-
- Iterable<VirtualFile> result = distributor.run();
- return result;
+ return distributor.run();
}
/**
@@ -255,13 +253,18 @@
// Generate the dex file contents.
List<Future<Boolean>> dexDataFutures = new ArrayList<>();
- Iterable<VirtualFile> virtualFiles = distribute(executorService);
+ List<VirtualFile> virtualFiles = distribute(executorService);
if (options.encodeChecksums) {
encodeChecksums(virtualFiles);
}
assert markers == null
|| markers.isEmpty()
|| application.dexItemFactory.extractMarkers() != null;
+ assert appView == null
+ || appView.withProtoShrinker(
+ shrinker ->
+ virtualFiles.stream().allMatch(shrinker::verifyDeadProtoTypesNotReferenced),
+ true);
// TODO(b/151313617): Sorting annotations mutates elements so run single threaded on main.
SortAnnotations sortAnnotations = new SortAnnotations(namingLens);
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 4eee167..7d2dbdc 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -240,6 +240,14 @@
transaction.commit();
}
+ public boolean containsString(DexString string) {
+ return indexedItems.strings.contains(string);
+ }
+
+ public boolean containsType(DexType type) {
+ return indexedItems.types.contains(type);
+ }
+
public boolean isEmpty() {
return indexedItems.classes.isEmpty();
}
@@ -258,7 +266,7 @@
this.writer = writer;
}
- public abstract Collection<VirtualFile> run() throws ExecutionException, IOException;
+ public abstract List<VirtualFile> run() throws ExecutionException, IOException;
}
/**
@@ -277,7 +285,7 @@
}
@Override
- public Collection<VirtualFile> run() {
+ public List<VirtualFile> run() {
HashMap<DexProgramClass, VirtualFile> files = new HashMap<>();
Collection<DexProgramClass> synthetics = new ArrayList<>();
// Assign dedicated virtual files for all program classes.
@@ -468,7 +476,7 @@
}
@Override
- public Collection<VirtualFile> run() throws IOException {
+ public List<VirtualFile> run() throws IOException {
int totalClassNumber = classes.size();
// First fill required classes into the main dex file.
fillForMainDexList(classes);
@@ -531,7 +539,7 @@
}
@Override
- public Collection<VirtualFile> run() throws ExecutionException, IOException {
+ public List<VirtualFile> run() throws ExecutionException, IOException {
Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses =
removeFeatureSplitClassesGetMapping();
// Add all classes to the main dex file.
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 55c4ccb..3d9e26c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -135,12 +135,16 @@
public boolean isSubtype(DexType subtype, DexType supertype) {
assert subtype != null;
assert supertype != null;
+ assert subtype.isClassType();
+ assert supertype.isClassType();
return subtype == supertype || isStrictSubtypeOf(subtype, supertype);
}
public boolean isStrictSubtypeOf(DexType subtype, DexType supertype) {
assert subtype != null;
assert supertype != null;
+ assert subtype.isClassType();
+ assert supertype.isClassType();
if (subtype == supertype) {
return false;
}
@@ -151,7 +155,6 @@
if (supertype == dexItemFactory().objectType) {
return true;
}
- // TODO(b/147658738): Clean up the code to not call on non-class types or fix this.
if (!subtype.isClassType() || !supertype.isClassType()) {
return false;
}
@@ -166,12 +169,21 @@
.shouldBreak();
}
- public boolean isRelatedBySubtyping(DexType type, DexType other) {
+ public boolean inSameHierarchy(DexType type, DexType other) {
assert type.isClassType();
assert other.isClassType();
return isSubtype(type, other) || isSubtype(other, type);
}
+ public boolean inDifferentHierarchy(DexType type1, DexType type2) {
+ return !inSameHierarchy(type1, type2);
+ }
+
+ public boolean isMissingOrHasMissingSuperType(DexType type) {
+ DexClass clazz = definitionFor(type);
+ return clazz == null || clazz.hasMissingSuperType(this);
+ }
+
/** Collect all interfaces that this type directly or indirectly implements. */
public Set<DexType> implementedInterfaces(DexType type) {
assert type.isClassType();
@@ -265,6 +277,39 @@
return relationChain;
}
+ public boolean methodDefinedInInterfaces(DexEncodedMethod method, DexType implementingClass) {
+ DexClass holder = definitionFor(implementingClass);
+ if (holder == null) {
+ return false;
+ }
+ for (DexType iface : holder.interfaces.values) {
+ if (methodDefinedInInterface(method, iface)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean methodDefinedInInterface(DexEncodedMethod method, DexType iface) {
+ DexClass potentialHolder = definitionFor(iface);
+ if (potentialHolder == null) {
+ return false;
+ }
+ assert potentialHolder.isInterface();
+ for (DexEncodedMethod virtualMethod : potentialHolder.virtualMethods()) {
+ if (virtualMethod.method.hasSameProtoAndName(method.method)
+ && virtualMethod.accessFlags.isSameVisibility(method.accessFlags)) {
+ return true;
+ }
+ }
+ for (DexType parentInterface : potentialHolder.interfaces.values) {
+ if (methodDefinedInInterface(method, parentInterface)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Helper method used for emulated interface resolution (not in JVM specifications). The result
* may be abstract.
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 4edc070..75ee97e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -5,7 +5,6 @@
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.WorkList;
@@ -147,11 +146,6 @@
// Map from types to their subtyping information.
private final Map<DexType, TypeInfo> typeInfo;
- // Caches which static types that may store an object that has a non-default finalize() method.
- // E.g., `java.lang.Object -> TRUE` if there is a subtype of Object that overrides finalize().
- private final Map<DexType, Boolean> mayHaveFinalizeMethodDirectlyOrIndirectlyCache =
- new ConcurrentHashMap<>();
-
public AppInfoWithSubtyping(DirectMappedDexApplication application) {
this(application, application.allClasses());
}
@@ -357,51 +351,6 @@
return true;
}
- public boolean isInstantiatedInterface(DexProgramClass clazz) {
- assert checkIfObsolete();
- return true; // Don't know, there might be.
- }
-
- public boolean methodDefinedInInterfaces(DexEncodedMethod method, DexType implementingClass) {
- DexClass holder = definitionFor(implementingClass);
- if (holder == null) {
- return false;
- }
- for (DexType iface : holder.interfaces.values) {
- if (methodDefinedInInterface(method, iface)) {
- return true;
- }
- }
- return false;
- }
-
- public boolean methodDefinedInInterface(DexEncodedMethod method, DexType iface) {
- DexClass potentialHolder = definitionFor(iface);
- if (potentialHolder == null) {
- return false;
- }
- assert potentialHolder.isInterface();
- for (DexEncodedMethod virtualMethod : potentialHolder.virtualMethods()) {
- if (virtualMethod.method.hasSameProtoAndName(method.method)
- && virtualMethod.accessFlags.isSameVisibility(method.accessFlags)) {
- return true;
- }
- }
- for (DexType parentInterface : potentialHolder.interfaces.values) {
- if (methodDefinedInInterface(method, parentInterface)) {
- return true;
- }
- }
- return false;
- }
-
- public boolean isStringConcat(DexMethodHandle bootstrapMethod) {
- assert checkIfObsolete();
- return bootstrapMethod.type.isInvokeStatic()
- && (bootstrapMethod.asMethod() == dexItemFactory().stringConcatWithConstantsMethod
- || bootstrapMethod.asMethod() == dexItemFactory().stringConcatMethod);
- }
-
private void registerNewType(DexType newType, DexType superType) {
assert checkIfObsolete();
// Register the relationship between this type and its superType.
@@ -429,10 +378,6 @@
return getTypeInfo(type).directSubtypes;
}
- public boolean isUnknown(DexType type) {
- return getTypeInfo(type).isUnknown();
- }
-
public boolean hasSubtypes(DexType type) {
return !getTypeInfo(type).directSubtypes.isEmpty();
}
@@ -460,11 +405,6 @@
return ImmutableList.of();
}
- public boolean isMissingOrHasMissingSuperType(DexType type) {
- DexClass clazz = definitionFor(type);
- return clazz == null || clazz.hasMissingSuperType(this);
- }
-
// TODO(b/139464956): Remove this method.
public DexType getSingleSubtype_(DexType type) {
TypeInfo info = getTypeInfo(type);
@@ -475,59 +415,4 @@
return null;
}
}
-
- public boolean inDifferentHierarchy(DexType type1, DexType type2) {
- return !isSubtype(type1, type2) && !isSubtype(type2, type1);
- }
-
- public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeElement type) {
- if (type.getClassType() == dexItemFactory().objectType && !type.getInterfaces().isEmpty()) {
- for (DexType interfaceType : type.getInterfaces()) {
- if (computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(interfaceType, false)) {
- return true;
- }
- }
- return false;
- }
- return computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(type.getClassType(), true);
- }
-
- private boolean computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(
- DexType type, boolean lookUpwards) {
- assert type.isClassType();
- Boolean cache = mayHaveFinalizeMethodDirectlyOrIndirectlyCache.get(type);
- if (cache != null) {
- return cache;
- }
- DexClass clazz = definitionFor(type);
- if (clazz == null) {
- // This is strictly not conservative but is needed to avoid that we treat Object as having
- // a subtype that has a non-default finalize() implementation.
- mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, false);
- return false;
- }
- if (clazz.isProgramClass()) {
- if (lookUpwards) {
- DexEncodedMethod resolutionResult =
- resolveMethod(type, dexItemFactory().objectMembers.finalize).getSingleTarget();
- if (resolutionResult != null && resolutionResult.isProgramMethod(this)) {
- mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, true);
- return true;
- }
- } else {
- if (clazz.lookupVirtualMethod(dexItemFactory().objectMembers.finalize) != null) {
- mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, true);
- return true;
- }
- }
- }
- for (DexType subtype : allImmediateSubtypes(type)) {
- if (computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(subtype, false)) {
- mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, true);
- return true;
- }
- }
- mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, false);
- return false;
- }
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 0e4b9cf..3a55054 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -27,7 +27,6 @@
import com.google.common.base.Predicates;
import java.util.IdentityHashMap;
import java.util.Map;
-import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -252,8 +251,22 @@
return protoShrinker;
}
- public void withGeneratedExtensionRegistryShrinker(
- Consumer<GeneratedExtensionRegistryShrinker> consumer) {
+ public <E extends Throwable> void withProtoShrinker(ThrowingConsumer<ProtoShrinker, E> consumer)
+ throws E {
+ if (protoShrinker != null) {
+ consumer.accept(protoShrinker);
+ }
+ }
+
+ public <U> U withProtoShrinker(Function<ProtoShrinker, U> consumer, U defaultValue) {
+ if (protoShrinker != null) {
+ return consumer.apply(protoShrinker);
+ }
+ return defaultValue;
+ }
+
+ public <E extends Throwable> void withGeneratedExtensionRegistryShrinker(
+ ThrowingConsumer<GeneratedExtensionRegistryShrinker, E> consumer) throws E {
if (protoShrinker != null && protoShrinker.generatedExtensionRegistryShrinker != null) {
consumer.accept(protoShrinker.generatedExtensionRegistryShrinker);
}
@@ -267,7 +280,8 @@
return defaultValue;
}
- public void withGeneratedMessageLiteShrinker(Consumer<GeneratedMessageLiteShrinker> consumer) {
+ public <E extends Throwable> void withGeneratedMessageLiteShrinker(
+ ThrowingConsumer<GeneratedMessageLiteShrinker, E> consumer) throws E {
if (protoShrinker != null && protoShrinker.generatedMessageLiteShrinker != null) {
consumer.accept(protoShrinker.generatedMessageLiteShrinker);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index f8043c3..32e1d61 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -150,8 +150,8 @@
methodCollection.addDirectMethods(methods);
}
- public void removeMethod(DexMethod method) {
- methodCollection.removeMethod(method);
+ public DexEncodedMethod removeMethod(DexMethod method) {
+ return methodCollection.removeMethod(method);
}
public void setDirectMethods(DexEncodedMethod[] methods) {
@@ -559,7 +559,7 @@
return getInitializer(DexType.EMPTY_ARRAY);
}
- public boolean hasMissingSuperType(AppInfoWithSubtyping appInfo) {
+ public boolean hasMissingSuperType(AppInfoWithClassHierarchy appInfo) {
if (superType != null && appInfo.isMissingOrHasMissingSuperType(superType)) {
return true;
}
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 03f1415..28a0da2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -252,7 +252,6 @@
this.code = code;
this.classFileVersion = classFileVersion;
this.d8R8Synthesized = d8R8Synthesized;
-
assert code == null || !shouldNotHaveCode();
assert parameterAnnotationsList != null;
}
@@ -311,6 +310,10 @@
return accessFlags.isFinal();
}
+ public boolean isPublic() {
+ return accessFlags.isPublic();
+ }
+
public boolean isInitializer() {
checkIfObsolete();
return isInstanceInitializer() || isClassInitializer();
@@ -1126,8 +1129,8 @@
return new DexEncodedMethod(
newMethod,
newFlags,
- target.annotations(),
- target.parameterAnnotationsList,
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
new SynthesizedCode(forwardSourceCodeBuilder::build),
true);
}
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 b2d7140..ffa702a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
+import static com.android.tools.r8.ir.optimize.ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME;
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.dex.Constants;
@@ -355,7 +356,8 @@
createStaticallyKnownType(invocationHandlerDescriptor);
public final DexType proxyType = createStaticallyKnownType(proxyDescriptor);
public final DexType serviceLoaderType = createStaticallyKnownType(serviceLoaderDescriptor);
-
+ public final DexType serviceLoaderRewrittenClassType =
+ createStaticallyKnownType("L" + SERVICE_LOADER_CLASS_NAME + ";");
public final DexType serviceLoaderConfigurationErrorType =
createStaticallyKnownType(serviceLoaderConfigurationErrorDescriptor);
public final DexType listType = createStaticallyKnownType(listDescriptor);
@@ -683,6 +685,7 @@
Stream.of(new Pair<>(npeMethods.initWithMessage, alwaysTrue())),
Stream.of(new Pair<>(objectMembers.constructor, alwaysTrue())),
Stream.of(new Pair<>(objectMembers.getClass, alwaysTrue())),
+ Stream.of(new Pair<>(stringMembers.hashCode, alwaysTrue())),
mapToPredicate(classMethods.getNames, alwaysTrue()),
mapToPredicate(
stringBufferMethods.constructorMethods,
@@ -1809,6 +1812,10 @@
}
}
+ public boolean isPossiblyCompilerSynthesizedType(DexType type) {
+ return possibleCompilerSynthesizedTypes.contains(type);
+ }
+
public void forEachPossiblyCompilerSynthesizedType(Consumer<DexType> fn) {
possibleCompilerSynthesizedTypes.forEach(fn);
}
diff --git a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
index 23bfb99..e5f7c51 100644
--- a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.graph;
import com.google.common.collect.ImmutableMap;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -77,10 +78,10 @@
public static final class EnumValueInfoMap {
- private final Map<DexField, EnumValueInfo> map;
+ private final LinkedHashMap<DexField, EnumValueInfo> map;
- public EnumValueInfoMap(Map<DexField, EnumValueInfo> map) {
- this.map = ImmutableMap.copyOf(map);
+ public EnumValueInfoMap(LinkedHashMap<DexField, EnumValueInfo> map) {
+ this.map = map;
}
public int size() {
@@ -100,11 +101,11 @@
}
EnumValueInfoMap rewrittenWithLens(GraphLense lens) {
- ImmutableMap.Builder<DexField, EnumValueInfo> builder = ImmutableMap.builder();
+ LinkedHashMap<DexField, EnumValueInfo> rewritten = new LinkedHashMap<>();
map.forEach(
(field, valueInfo) ->
- builder.put(lens.lookupField(field), valueInfo.rewrittenWithLens(lens)));
- return new EnumValueInfoMap(builder.build());
+ rewritten.put(lens.lookupField(field), valueInfo.rewrittenWithLens(lens)));
+ return new EnumValueInfoMap(rewritten);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index ed2f612..ec3e102 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -181,7 +181,7 @@
backing.addDirectMethods(methods);
}
- public void removeMethod(DexMethod method) {
+ public DexEncodedMethod removeMethod(DexMethod method) {
DexEncodedMethod removed = backing.removeMethod(method);
if (removed != null) {
if (backing.belongsToDirectPool(removed)) {
@@ -191,6 +191,7 @@
resetVirtualMethodCaches();
}
}
+ return removed;
}
public void setDirectMethods(DexEncodedMethod[] methods) {
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 5999bbf..34f32ff 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.shaking.InstantiationReason;
import com.android.tools.r8.shaking.KeepReason;
import com.android.tools.r8.utils.LensUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -21,6 +22,7 @@
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Provides information about all possibly instantiated classes and lambdas, their allocation sites,
@@ -140,6 +142,24 @@
Consumer<DexProgramClass> onClass,
Consumer<LambdaDescriptor> onLambda,
AppInfo appInfo) {
+ traverseInstantiatedSubtypes(
+ type,
+ clazz -> {
+ onClass.accept(clazz);
+ return TraversalContinuation.CONTINUE;
+ },
+ lambda -> {
+ onLambda.accept(lambda);
+ return TraversalContinuation.CONTINUE;
+ },
+ appInfo);
+ }
+
+ public TraversalContinuation traverseInstantiatedSubtypes(
+ DexType type,
+ Function<DexProgramClass, TraversalContinuation> onClass,
+ Function<LambdaDescriptor, TraversalContinuation> onLambda,
+ AppInfo appInfo) {
WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
if (type == appInfo.dexItemFactory().objectType) {
// All types are below java.lang.Object, but we don't maintain an entry for it.
@@ -157,7 +177,12 @@
// If no definition for the type is found, populate the worklist with any
// instantiated subtypes and callback with any lambda instance.
worklist.addIfNotSeen(instantiatedHierarchy.getOrDefault(type, Collections.emptySet()));
- instantiatedLambdas.getOrDefault(type, Collections.emptyList()).forEach(onLambda);
+ for (LambdaDescriptor lambda :
+ instantiatedLambdas.getOrDefault(type, Collections.emptyList())) {
+ if (onLambda.apply(lambda).shouldBreak()) {
+ return TraversalContinuation.BREAK;
+ }
+ }
} else {
worklist.addIfNotSeen(initialClass);
}
@@ -169,12 +194,20 @@
DexProgramClass programClass = clazz.asProgramClass();
if (isInstantiatedDirectly(programClass)
|| isInterfaceWithUnknownSubtypeHierarchy(programClass)) {
- onClass.accept(programClass);
+ if (onClass.apply(programClass).shouldBreak()) {
+ return TraversalContinuation.BREAK;
+ }
}
}
worklist.addIfNotSeen(instantiatedHierarchy.getOrDefault(clazz.type, Collections.emptySet()));
- instantiatedLambdas.getOrDefault(clazz.type, Collections.emptyList()).forEach(onLambda);
+ for (LambdaDescriptor lambda :
+ instantiatedLambdas.getOrDefault(clazz.type, Collections.emptyList())) {
+ if (onLambda.apply(lambda).shouldBreak()) {
+ return TraversalContinuation.BREAK;
+ }
+ }
}
+ return TraversalContinuation.CONTINUE;
}
public static class Builder extends ObjectAllocationInfoCollectionImpl {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index 0bb8f21..be72b24 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -222,9 +222,14 @@
} else if (jumpInstruction.isStringSwitch()) {
StringSwitch switchInst = jumpInstruction.asStringSwitch();
LatticeElement switchElement = getLatticeElement(switchInst.value());
-
- // There is currently no constant propagation for strings.
- assert !switchElement.isConst();
+ if (switchElement.isConst()) {
+ // There is currently no constant propagation for strings, so it must be null.
+ assert switchElement.asConst().getConstNumber().isZero();
+ BasicBlock target = switchInst.fallthroughBlock();
+ setExecutableEdge(jumpInstBlockNumber, target.getNumber());
+ flowEdges.add(target);
+ return;
+ }
} else {
assert jumpInstruction.isGoto() || jumpInstruction.isReturn() || jumpInstruction.isThrow();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 08b29ce..b4e025c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.analysis.proto;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.google.common.base.Predicates.not;
import com.android.tools.r8.graph.AppView;
@@ -32,6 +33,7 @@
import com.android.tools.r8.shaking.TreePrunerConfiguration;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
@@ -40,6 +42,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -156,26 +160,29 @@
return removedExtensionFields.contains(field);
}
- public void postOptimizeGeneratedExtensionRegistry(IRConverter converter, Timing timing) {
+ public void postOptimizeGeneratedExtensionRegistry(
+ IRConverter converter, ExecutorService executorService, Timing timing)
+ throws ExecutionException {
timing.begin("[Proto] Post optimize generated extension registry");
- forEachFindLiteExtensionByNumberMethod(
+ ThreadUtils.processItems(
+ this::forEachFindLiteExtensionByNumberMethod,
method ->
converter.processMethod(
method,
OptimizationFeedbackIgnore.getInstance(),
- OneTimeMethodProcessor.getInstance()));
- timing.end(); // [Proto] Post optimize generated extension registry
+ OneTimeMethodProcessor.getInstance()),
+ executorService);
+ timing.end();
}
private void forEachFindLiteExtensionByNumberMethod(Consumer<DexEncodedMethod> consumer) {
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (clazz.superType != references.extensionRegistryLiteType) {
- continue;
- }
-
- for (DexEncodedMethod method : clazz.methods()) {
- if (references.isFindLiteExtensionByNumberMethod(method.method)) {
- consumer.accept(method);
+ for (DexType type : appView.appInfo().subtypes(references.extensionRegistryLiteType)) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ if (clazz != null) {
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (references.isFindLiteExtensionByNumberMethod(method.method)) {
+ consumer.accept(method);
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 0801f16..2914d30 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -20,8 +20,8 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.IntSwitch;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.conversion.IRConverter;
@@ -36,7 +36,6 @@
import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -65,16 +64,11 @@
/** Returns true if an action was deferred. */
public boolean deferDeadProtoBuilders(
- DexProgramClass clazz,
- DexEncodedMethod context,
- BooleanSupplier register,
- Enqueuer enqueuer) {
+ DexProgramClass clazz, DexEncodedMethod context, BooleanSupplier register) {
if (references.isDynamicMethod(context) && references.isGeneratedMessageLiteBuilder(clazz)) {
if (register.getAsBoolean()) {
- if (enqueuer.getMode().isFinalTreeShaking()) {
- assert builders.getOrDefault(clazz, context) == context;
- builders.put(clazz, context);
- }
+ assert builders.getOrDefault(clazz, context) == context;
+ builders.put(clazz, context);
return true;
}
}
@@ -263,7 +257,11 @@
}
@Override
- public boolean switchCaseIsUnreachable(IntSwitch theSwitch, int index) {
+ public boolean switchCaseIsUnreachable(Switch theSwitch, int index) {
+ if (theSwitch.isStringSwitch()) {
+ assert false : "Unexpected string-switch instruction in dynamicMethod()";
+ return false;
+ }
if (index != newBuilderOrdinal) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index b2d813a..40b8cb5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.analysis.proto;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getInfoValueFromMessageInfoConstructionInvoke;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getObjectsValueFromMessageInfoConstructionInvoke;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.setObjectsValueForMessageInfoConstructionInvoke;
@@ -11,7 +12,10 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoMessageInfo;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoObject;
import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -32,8 +36,11 @@
import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
public class GeneratedMessageLiteShrinker {
@@ -70,22 +77,33 @@
}
}
- public void postOptimizeDynamicMethods(IRConverter converter, Timing timing) {
+ public void postOptimizeDynamicMethods(
+ IRConverter converter, ExecutorService executorService, Timing timing)
+ throws ExecutionException {
timing.begin("[Proto] Post optimize dynamic methods");
- forEachDynamicMethod(
+ ThreadUtils.processItems(
+ this::forEachDynamicMethod,
method ->
converter.processMethod(
method,
OptimizationFeedbackIgnore.getInstance(),
- OneTimeMethodProcessor.getInstance()));
+ OneTimeMethodProcessor.getInstance()),
+ executorService);
timing.end();
}
private void forEachDynamicMethod(Consumer<DexEncodedMethod> consumer) {
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- DexEncodedMethod dynamicMethod = clazz.lookupVirtualMethod(references::isDynamicMethod);
- if (dynamicMethod != null) {
- consumer.accept(dynamicMethod);
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ for (DexType type : appView.appInfo().subtypes(references.generatedMessageLiteType)) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ if (clazz != null) {
+ DexMethod dynamicMethod =
+ dexItemFactory.createMethod(
+ type, references.dynamicMethodProto, references.dynamicMethodName);
+ DexEncodedMethod encodedDynamicMethod = clazz.lookupVirtualMethod(dynamicMethod);
+ if (encodedDynamicMethod != null) {
+ consumer.accept(encodedDynamicMethod);
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index b7b8ef1..78f9e6e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -4,9 +4,14 @@
package com.android.tools.r8.ir.analysis.proto;
+import com.android.tools.r8.dex.VirtualFile;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoFieldTypeFactory;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
+import java.util.Set;
public class ProtoShrinker {
@@ -17,6 +22,8 @@
public final GeneratedMessageLiteBuilderShrinker generatedMessageLiteBuilderShrinker;
public final ProtoReferences references;
+ private Set<DexType> deadProtoTypes = Sets.newIdentityHashSet();
+
public ProtoShrinker(AppView<AppInfoWithLiveness> appView) {
ProtoFieldTypeFactory factory = new ProtoFieldTypeFactory();
ProtoReferences references = new ProtoReferences(appView.dexItemFactory());
@@ -36,4 +43,22 @@
: null;
this.references = references;
}
+
+ public Set<DexType> getDeadProtoTypes() {
+ return deadProtoTypes;
+ }
+
+ public void setDeadProtoTypes(Set<DexType> deadProtoTypes) {
+ // We should only need to keep track of the dead proto types for assertion purposes.
+ InternalOptions.checkAssertionsEnabled();
+ this.deadProtoTypes = deadProtoTypes;
+ }
+
+ public boolean verifyDeadProtoTypesNotReferenced(VirtualFile virtualFile) {
+ for (DexType deadProtoType : deadProtoTypes) {
+ assert !virtualFile.containsString(deadProtoType.descriptor);
+ assert !virtualFile.containsType(deadProtoType);
+ }
+ return true;
+ }
}
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 9dea6f8..4bd58c4 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
@@ -253,6 +253,13 @@
return successors.size();
}
+ public int numberOfExceptionalSuccessors() {
+ if (hasCatchHandlers()) {
+ return catchHandlers.getUniqueTargets().size();
+ }
+ return 0;
+ }
+
public boolean hasUniquePredecessor() {
return predecessors.size() == 1;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 1537f85..32c5602 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -223,6 +224,16 @@
}
@Override
+ public Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value) {
+ ConstString constStringInstruction = code.createStringConstant(appView, value);
+ // Note that we only keep position info for throwing instructions in release mode.
+ constStringInstruction.setPosition(
+ appView.options().debug ? current.getPosition() : Position.none());
+ add(constStringInstruction);
+ return constStringInstruction.outValue();
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
if (current == null) {
throw new IllegalStateException();
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 06aef0a..fcb0c1b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -43,6 +43,10 @@
this.value = value;
}
+ public static ConstNumber asConstNumberOrNull(Instruction instruction) {
+ return (ConstNumber) instruction;
+ }
+
@Override
public int opcode() {
return Opcodes.CONST_NUMBER;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 268c065..064842a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -3,11 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.TypeChecker;
@@ -15,6 +18,7 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.origin.Origin;
@@ -29,6 +33,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@@ -982,6 +987,10 @@
return this::instructionIterator;
}
+ public Stream<Instruction> streamInstructions() {
+ return Streams.stream(instructions());
+ }
+
public <T extends Instruction> Iterable<T> instructions(Predicate<Instruction> predicate) {
return () -> IteratorUtils.filter(instructionIterator(), predicate);
}
@@ -1098,6 +1107,16 @@
return new ConstNumber(out, value);
}
+ public ConstString createStringConstant(AppView<?> appView, DexString value) {
+ return createStringConstant(appView, value, null);
+ }
+
+ public ConstString createStringConstant(
+ AppView<?> appView, DexString value, DebugLocalInfo local) {
+ Value out = createValue(TypeElement.stringClassType(appView, definitelyNotNull()), local);
+ return new ConstString(out, value, ThrowingInfo.defaultForConstString(appView.options()));
+ }
+
public Phi createPhi(BasicBlock block, TypeElement type) {
return new Phi(valueNumberGenerator.next(), block, type, null, RegisterReadType.NORMAL);
}
@@ -1107,8 +1126,7 @@
}
public ConstClass createConstClass(AppView<?> appView, DexType type) {
- Value out =
- createValue(TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView));
+ Value out = createValue(TypeElement.fromDexType(type, definitelyNotNull(), appView));
return new ConstClass(out, type);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 7f8d5d3..03ca81d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.InternalOptions;
import java.util.ListIterator;
@@ -38,6 +39,11 @@
}
@Override
+ public Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value) {
+ return instructionIterator.insertConstStringInstruction(appView, code, value);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index e46af2c..c562fe7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -250,6 +250,16 @@
return get(Opcodes.SUB);
}
+ @SuppressWarnings("ConstantConditions")
+ public boolean mayHaveSwitch() {
+ assert Opcodes.INT_SWITCH < 64;
+ assert Opcodes.STRING_SWITCH < 64;
+ long mask = (1L << Opcodes.INT_SWITCH) | (1L << Opcodes.STRING_SWITCH);
+ boolean result = isAnySetInFirst(mask);
+ assert result == (mayHaveIntSwitch() || mayHaveStringSwitch());
+ return result;
+ }
+
public boolean mayHaveUshr() {
return get(Opcodes.USHR);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 623da4d..371438e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
@@ -64,6 +65,8 @@
Value insertConstIntInstruction(IRCode code, InternalOptions options, int value);
+ Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value);
+
void replaceCurrentInstructionWithConstInt(IRCode code, int value);
void replaceCurrentInstructionWithStaticGet(
diff --git a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
index f22ef1c..e0e3cdc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.code.SparseSwitch;
import com.android.tools.r8.code.SparseSwitchPayload;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
@@ -51,13 +52,18 @@
}
@Override
+ public Instruction materializeFirstKey(AppView<?> appView, IRCode code) {
+ return code.createIntConstant(getFirstKey());
+ }
+
+ @Override
public boolean valid() {
assert super.valid();
assert keys.length >= 1;
assert keys.length <= Constants.U16BIT_MAX;
// Keys must be acceding, and cannot target the fallthrough.
assert keys.length == numberOfKeys();
- for (int i = 1; i < keys.length - 1; i++) {
+ for (int i = 1; i < keys.length; i++) {
assert keys[i - 1] < keys[i];
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 2a82bcc..15700bf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.InternalOptions;
import java.util.ListIterator;
@@ -51,6 +52,11 @@
}
@Override
+ public Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value) {
+ return currentBlockIterator.insertConstStringInstruction(appView, code, value);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
currentBlockIterator.replaceCurrentInstructionWithConstInt(code, value);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 49779aa..26e808f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -170,7 +170,7 @@
+ "that is not defined on all control-flow paths leading to the use.");
}
- private void appendOperand(Value operand) {
+ public void appendOperand(Value operand) {
operands.add(operand);
operand.addPhiUser(this);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
index 1b3d673..e25685c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
@@ -7,10 +7,12 @@
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import java.util.function.BiConsumer;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
+import com.android.tools.r8.utils.ThrowingConsumer;
public class StringSwitch extends Switch {
@@ -23,6 +25,10 @@
assert valid();
}
+ public DexString getFirstKey() {
+ return keys[0];
+ }
+
@Override
public int opcode() {
return Opcodes.STRING_SWITCH;
@@ -33,13 +39,25 @@
return visitor.visit(this);
}
- public void forEachCase(BiConsumer<DexString, BasicBlock> fn) {
+ public <E extends Throwable> void forEachKey(ThrowingConsumer<DexString, E> fn) throws E {
+ for (DexString key : keys) {
+ fn.accept(key);
+ }
+ }
+
+ public <E extends Throwable> void forEachCase(ThrowingBiConsumer<DexString, BasicBlock, E> fn)
+ throws E {
for (int i = 0; i < keys.length; i++) {
fn.accept(getKey(i), targetBlock(i));
}
}
@Override
+ public Instruction materializeFirstKey(AppView<?> appView, IRCode code) {
+ return code.createStringConstant(appView, getFirstKey());
+ }
+
+ @Override
public boolean valid() {
assert super.valid();
assert keys.length >= 1;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 8dcb6a4..4e129aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.graph.AppView;
import java.util.function.Consumer;
public abstract class Switch extends JumpInstruction {
@@ -17,6 +18,8 @@
this.fallthroughBlockIndex = fallthroughBlockIndex;
}
+ public abstract Instruction materializeFirstKey(AppView<?> appView, IRCode code);
+
public Value value() {
return inValues.get(0);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 6550178..ae2f9d7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1340,11 +1340,6 @@
timing.begin("Propogate sparse conditionals");
new SparseConditionalConstantPropagation(code).run();
timing.end();
- if (stringSwitchRemover != null) {
- timing.begin("Remove string switch");
- stringSwitchRemover.run(method, code);
- timing.end();
- }
timing.begin("Rewrite always throwing invokes");
codeRewriter.processMethodsNeverReturningNormally(code);
timing.end();
@@ -1514,6 +1509,14 @@
previous = printMethod(code, "IR after outline handler (SSA)", previous);
+ if (stringSwitchRemover != null) {
+ // Remove string switches prior to canonicalization to ensure that the constants that are
+ // being introduced will be canonicalized if possible.
+ timing.begin("Remove string switch");
+ stringSwitchRemover.run(method, code);
+ timing.end();
+ }
+
// TODO(mkroghj) Test if shorten live ranges is worth it.
if (!options.isGeneratingClassFiles()) {
timing.begin("Canonicalize constants");
@@ -1649,6 +1652,9 @@
public void removeDeadCodeAndFinalizeIR(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+ if (stringSwitchRemover != null) {
+ stringSwitchRemover.run(method, code);
+ }
deadCodeRemover.run(code, timing);
finalizeIR(method, code, feedback, timing);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
index 0365d43..50bec23 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.conversion;
+import static com.android.tools.r8.ir.code.ConstNumber.asConstNumberOrNull;
+
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
@@ -24,11 +26,12 @@
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.io.UTFDataFormatException;
import java.util.ArrayList;
-import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -231,7 +234,7 @@
return null;
}
- Map<DexString, BasicBlock> stringToTargetMapping = new IdentityHashMap<>();
+ Map<DexString, BasicBlock> stringToTargetMapping = new LinkedHashMap<>();
for (DexString key : stringToIdMapping.mapping.keySet()) {
int id = stringToIdMapping.mapping.getInt(key);
BasicBlock target = idToTargetMapping.mapping.get(id);
@@ -378,7 +381,7 @@
InvokeVirtual invoke = instruction.asInvokeVirtual();
if (invoke.getInvokedMethod() == dexItemFactory.stringMembers.hashCode
&& invoke.getReceiver() == stringValue
- && invoke.outValue().onlyUsedInBlock(block)) {
+ && (!invoke.hasOutValue() || invoke.outValue().onlyUsedInBlock(block))) {
continue;
}
}
@@ -430,7 +433,7 @@
}
// Go into the true-target and record a mapping from all strings to their id.
- Reference2IntMap<DexString> extension = new Reference2IntOpenHashMap<>();
+ Reference2IntMap<DexString> extension = new Reference2IntLinkedOpenHashMap<>();
BasicBlock ifEqualsHashTarget = Utils.getTrueTarget(theIf);
if (!addMappingsForStringsWithHash(ifEqualsHashTarget, hash, extension)) {
// Not a valid extension of `toBeExtended`.
@@ -460,7 +463,7 @@
}
// Go into each switch case and record a mapping from all strings to their id.
- Reference2IntMap<DexString> extension = new Reference2IntOpenHashMap<>();
+ Reference2IntMap<DexString> extension = new Reference2IntLinkedOpenHashMap<>();
for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
int hash = theSwitch.getKey(i);
BasicBlock equalsHashTarget = theSwitch.targetBlock(i);
@@ -494,18 +497,36 @@
Set<BasicBlock> visited) {
InstructionIterator instructionIterator = block.iterator();
- // Verify that the first instruction is a non-throwing const-string instruction.
- // If the string throws, it can't be decoded, and then the string does not have a hash.
- ConstString theString = instructionIterator.next().asConstString();
- if (theString == null || theString.instructionInstanceCanThrow()) {
+ // The first instruction is expected to be a non-throwing const-string instruction, but this
+ // may change due to canonicalization. If the string throws, it can't be decoded, and then
+ // the string does not have a hash.
+ Instruction first = instructionIterator.next();
+ ConstString optionalString = first.asConstString();
+ if (optionalString != null && optionalString.instructionInstanceCanThrow()) {
return false;
}
- InvokeVirtual theInvoke = instructionIterator.next().asInvokeVirtual();
+ // The next instruction must be an invoke-virtual that calls stringValue.equals() with a
+ // constant string argument.
+ InvokeVirtual theInvoke =
+ first.isConstString()
+ ? instructionIterator.next().asInvokeVirtual()
+ : first.asInvokeVirtual();
if (theInvoke == null
|| theInvoke.getInvokedMethod() != dexItemFactory.stringMembers.equals
- || theInvoke.getReceiver() != stringValue
- || theInvoke.inValues().get(1) != theString.outValue()) {
+ || theInvoke.getReceiver() != stringValue) {
+ return false;
+ }
+
+ // If this block starts with a const-string instruction, then it should be passed as the
+ // second argument to equals().
+ if (optionalString != null && theInvoke.getArgument(1) != optionalString.outValue()) {
+ assert false; // This should generally not happen.
+ return false;
+ }
+
+ Value theString = theInvoke.getArgument(1).getAliasedValue();
+ if (!theString.isDefinedByInstructionSatisfying(Instruction::isConstString)) {
return false;
}
@@ -518,9 +539,10 @@
}
try {
- if (theString.getValue().decodedHashCode() == hash) {
- BasicBlock trueTarget = theIf.targetFromCondition(1).endOfGotoChain();
- if (!addMappingForString(trueTarget, theString.getValue(), extension)) {
+ DexString theStringValue = theString.definition.asConstString().getValue();
+ if (theStringValue.decodedHashCode() == hash) {
+ BasicBlock trueTarget = theIf.targetFromCondition(1);
+ if (!addMappingForString(trueTarget, theStringValue, extension)) {
return false;
}
}
@@ -545,7 +567,17 @@
private boolean addMappingForString(
BasicBlock block, DexString string, Reference2IntMap<DexString> extension) {
InstructionIterator instructionIterator = block.iterator();
- ConstNumber constNumberInstruction = instructionIterator.next().asConstNumber();
+ ConstNumber constNumberInstruction;
+ if (block.isTrivialGoto()) {
+ if (block.getUniqueNormalSuccessor() != idValue.getBlock()) {
+ return false;
+ }
+ int predecessorIndex = idValue.getBlock().getPredecessors().indexOf(block);
+ constNumberInstruction =
+ asConstNumberOrNull(idValue.getOperand(predecessorIndex).definition);
+ } else {
+ constNumberInstruction = instructionIterator.next().asConstNumber();
+ }
if (constNumberInstruction == null
|| !idValue.getOperands().contains(constNumberInstruction.outValue())) {
return false;
@@ -568,7 +600,7 @@
// The hash value of interest.
private final Value stringHashValue;
- private final Reference2IntMap<DexString> mapping = new Reference2IntOpenHashMap<>();
+ private final Reference2IntMap<DexString> mapping = new Reference2IntLinkedOpenHashMap<>();
private StringToIdMapping(Value stringHashValue, DexItemFactory dexItemFactory) {
assert isDefinedByStringHashCode(stringHashValue, dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index fcfc1e9..ebce25c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -4,11 +4,14 @@
package com.android.tools.r8.ir.conversion;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.naming.IdentifierNameStringUtils.isClassNameValue;
+
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -17,17 +20,27 @@
import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.IntSwitch;
import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.JumpInstruction;
+import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StringSwitch;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.naming.IdentifierNameStringMarker;
+import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
-import java.util.IdentityHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceRBTreeMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.io.UTFDataFormatException;
+import java.util.LinkedHashMap;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
@@ -43,7 +56,7 @@
StringSwitchRemover(AppView<?> appView, IdentifierNameStringMarker identifierNameStringMarker) {
this.appView = appView;
this.identifierNameStringMarker = identifierNameStringMarker;
- this.stringType = TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
+ this.stringType = TypeElement.stringClassType(appView, definitelyNotNull());
this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
}
@@ -53,106 +66,426 @@
return;
}
- Set<BasicBlock> newBlocks = Sets.newIdentityHashSet();
+ if (!prepareForStringSwitchRemoval(code)) {
+ return;
+ }
+
+ Set<BasicBlock> newBlocksWithStrings = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
- JumpInstruction exit = block.exit();
- if (exit.isStringSwitch()) {
- removeStringSwitch(code, blockIterator, block, exit.asStringSwitch(), newBlocks);
+ StringSwitch theSwitch = block.exit().asStringSwitch();
+ if (theSwitch != null) {
+ try {
+ SingleStringSwitchRemover remover;
+ if (theSwitch.numberOfKeys() < appView.options().minimumStringSwitchSize
+ || hashCodeOfKeysMayChangeAfterMinification(theSwitch)) {
+ remover =
+ new SingleEqualityBasedStringSwitchRemover(
+ code, blockIterator, block, theSwitch, newBlocksWithStrings);
+ } else {
+ remover =
+ new SingleHashBasedStringSwitchRemover(
+ code, blockIterator, block, theSwitch, newBlocksWithStrings);
+ }
+ remover.removeStringSwitch();
+ } catch (UTFDataFormatException e) {
+ // The keys of a string-switch should never fail to decode.
+ throw new Unreachable();
+ }
}
}
if (identifierNameStringMarker != null) {
- identifierNameStringMarker.decoupleIdentifierNameStringsInBlocks(method, code, newBlocks);
+ identifierNameStringMarker.decoupleIdentifierNameStringsInBlocks(
+ method, code, newBlocksWithStrings);
}
assert code.isConsistentSSA();
}
- private void removeStringSwitch(
- IRCode code,
- ListIterator<BasicBlock> blockIterator,
- BasicBlock block,
- StringSwitch theSwitch,
- Set<BasicBlock> newBlocks) {
- int nextBlockNumber = code.getHighestBlockNumber() + 1;
+ // Returns true if minification is enabled and the switch value is guaranteed to be a class name.
+ // In this case, we can't use the hash codes of the keys before minification, because they will
+ // (potentially) change as a result of minification. Therefore, we currently emit a sequence of
+ // if-equals checks for such switches.
+ //
+ // TODO(b/154483187): This should also use the hash-based string switch elimination.
+ private boolean hashCodeOfKeysMayChangeAfterMinification(StringSwitch theSwitch) {
+ return appView.options().isMinifying()
+ && isClassNameValue(theSwitch.value(), appView.dexItemFactory());
+ }
- BasicBlock fallthroughBlock = theSwitch.fallthroughBlock();
- Map<DexString, BasicBlock> stringToTargetMap = new IdentityHashMap<>();
- theSwitch.forEachCase(stringToTargetMap::put);
+ private boolean prepareForStringSwitchRemoval(IRCode code) {
+ boolean hasStringSwitch = false;
+ ListIterator<BasicBlock> blockIterator = code.listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ for (BasicBlock predecessor : block.getNormalPredecessors()) {
+ StringSwitch exit = predecessor.exit().asStringSwitch();
+ if (exit != null) {
+ hasStringSwitch = true;
+ if (block == exit.fallthroughBlock()) {
+ // After the elimination of this string-switch instruction, there will be two
+ // fallthrough blocks: one for the instruction that switches on the hash value and one
+ // for the instruction that switches on the string id value.
+ //
+ // The existing fallthrough block will be the fallthrough block for the switch on the
+ // hash value. Note that we can't use this block for the switch on the id value since
+ // that would lead to critical edges.
+ BasicBlock hashSwitchFallthroughBlock = block;
- // Remove outgoing control flow edges from the block containing the string switch.
- for (BasicBlock successor : block.getNormalSuccessors()) {
- successor.removePredecessor(block, null);
- }
- block.removeAllNormalSuccessors();
+ // The `hashSwitchFallthroughBlock` will jump to the switch on the string id value.
+ // This block will have multiple predecessors, hence the need for the split-edge
+ // block.
+ BasicBlock idSwitchBlock =
+ hashSwitchFallthroughBlock.listIterator(code).split(code, blockIterator);
- Set<BasicBlock> blocksTargetedByMultipleSwitchCases = Sets.newIdentityHashSet();
- {
- Set<BasicBlock> seenBefore = SetUtils.newIdentityHashSet(stringToTargetMap.size());
- for (BasicBlock targetBlock : stringToTargetMap.values()) {
- if (!seenBefore.add(targetBlock)) {
- blocksTargetedByMultipleSwitchCases.add(targetBlock);
+ // Split again such that `idSwitchBlock` becomes a block consisting of a single goto
+ // instruction that targets a block that is identical to the original fallthrough
+ // block of the string-switch instruction.
+ BasicBlock idSwitchFallthroughBlock =
+ idSwitchBlock.listIterator(code).split(code, blockIterator);
+ break;
+ }
}
}
}
- // Create a String.equals() check for each case in the string-switch instruction.
- BasicBlock previous = null;
- for (Entry<DexString, BasicBlock> entry : stringToTargetMap.entrySet()) {
- ConstString constStringInstruction =
- new ConstString(code.createValue(stringType), entry.getKey(), throwingInfo);
- constStringInstruction.setPosition(Position.syntheticNone());
+ return hasStringSwitch;
+ }
- InvokeVirtual invokeInstruction =
- new InvokeVirtual(
- appView.dexItemFactory().stringMembers.equals,
- code.createValue(PrimitiveTypeElement.getInt()),
- ImmutableList.of(theSwitch.value(), constStringInstruction.outValue()));
- invokeInstruction.setPosition(Position.syntheticNone());
+ private abstract static class SingleStringSwitchRemover {
- If ifInstruction = new If(If.Type.NE, invokeInstruction.outValue());
- ifInstruction.setPosition(Position.none());
+ final IRCode code;
+ final ListIterator<BasicBlock> blockIterator;
+ final Set<BasicBlock> newBlocksWithStrings;
- BasicBlock targetBlock = entry.getValue();
- if (blocksTargetedByMultipleSwitchCases.contains(targetBlock)) {
- // Need an intermediate block to avoid critical edges.
- BasicBlock intermediateBlock =
- BasicBlock.createGotoBlock(nextBlockNumber++, Position.none(), code.metadata());
- intermediateBlock.link(targetBlock);
- blockIterator.add(intermediateBlock);
- newBlocks.add(intermediateBlock);
- targetBlock = intermediateBlock;
- }
+ final Position position;
+ final Value stringValue;
- BasicBlock newBlock =
- BasicBlock.createIfBlock(
- nextBlockNumber++,
- ifInstruction,
- code.metadata(),
- constStringInstruction,
- invokeInstruction);
- newBlock.link(targetBlock);
- blockIterator.add(newBlock);
- newBlocks.add(newBlock);
-
- if (previous == null) {
- // Replace the string-switch instruction by a goto instruction.
- block.exit().replace(new Goto(newBlock), code);
- block.link(newBlock);
- } else {
- // Set the fallthrough block for the previously added if-instruction.
- previous.link(newBlock);
- }
-
- previous = newBlock;
+ private SingleStringSwitchRemover(
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ StringSwitch theSwitch,
+ Set<BasicBlock> newBlocksWithStrings) {
+ this.code = code;
+ this.blockIterator = blockIterator;
+ this.newBlocksWithStrings = newBlocksWithStrings;
+ this.position = theSwitch.getPosition();
+ this.stringValue = theSwitch.value();
}
- assert previous != null;
+ abstract void removeStringSwitch();
+ }
- // Set the fallthrough block for the last if-instruction.
- previous.link(fallthroughBlock);
+ private class SingleEqualityBasedStringSwitchRemover extends SingleStringSwitchRemover {
+
+ private final BasicBlock block;
+ private final BasicBlock fallthroughBlock;
+
+ private final Map<DexString, BasicBlock> structure;
+
+ private SingleEqualityBasedStringSwitchRemover(
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ BasicBlock block,
+ StringSwitch theSwitch,
+ Set<BasicBlock> newBlocksWithStrings) {
+ super(code, blockIterator, theSwitch, newBlocksWithStrings);
+ this.block = block;
+ this.fallthroughBlock = theSwitch.fallthroughBlock();
+ this.structure = createStructure(theSwitch);
+ }
+
+ private Map<DexString, BasicBlock> createStructure(StringSwitch theSwitch) {
+ Map<DexString, BasicBlock> result = new LinkedHashMap<>();
+ theSwitch.forEachCase(result::put);
+ return result;
+ }
+
+ @Override
+ void removeStringSwitch() {
+ int nextBlockNumber = code.getHighestBlockNumber() + 1;
+ // Remove outgoing control flow edges from the block containing the string switch.
+ for (BasicBlock successor : block.getNormalSuccessors()) {
+ successor.removePredecessor(block, null);
+ }
+ block.removeAllNormalSuccessors();
+ Set<BasicBlock> blocksTargetedByMultipleSwitchCases = Sets.newIdentityHashSet();
+ {
+ Set<BasicBlock> seenBefore = SetUtils.newIdentityHashSet(structure.size());
+ for (BasicBlock targetBlock : structure.values()) {
+ if (!seenBefore.add(targetBlock)) {
+ blocksTargetedByMultipleSwitchCases.add(targetBlock);
+ }
+ }
+ }
+ // Create a String.equals() check for each case in the string-switch instruction.
+ BasicBlock previous = null;
+ for (Entry<DexString, BasicBlock> entry : structure.entrySet()) {
+ ConstString constStringInstruction =
+ new ConstString(code.createValue(stringType), entry.getKey(), throwingInfo);
+ constStringInstruction.setPosition(position);
+ InvokeVirtual invokeInstruction =
+ new InvokeVirtual(
+ appView.dexItemFactory().stringMembers.equals,
+ code.createValue(PrimitiveTypeElement.getInt()),
+ ImmutableList.of(stringValue, constStringInstruction.outValue()));
+ invokeInstruction.setPosition(position);
+ If ifInstruction = new If(If.Type.NE, invokeInstruction.outValue());
+ ifInstruction.setPosition(Position.none());
+ BasicBlock targetBlock = entry.getValue();
+ if (blocksTargetedByMultipleSwitchCases.contains(targetBlock)) {
+ // Need an intermediate block to avoid critical edges.
+ BasicBlock intermediateBlock =
+ BasicBlock.createGotoBlock(nextBlockNumber++, Position.none(), code.metadata());
+ intermediateBlock.link(targetBlock);
+ blockIterator.add(intermediateBlock);
+ newBlocksWithStrings.add(intermediateBlock);
+ targetBlock = intermediateBlock;
+ }
+ BasicBlock newBlock =
+ BasicBlock.createIfBlock(
+ nextBlockNumber++,
+ ifInstruction,
+ code.metadata(),
+ constStringInstruction,
+ invokeInstruction);
+ newBlock.link(targetBlock);
+ blockIterator.add(newBlock);
+ newBlocksWithStrings.add(newBlock);
+ if (previous == null) {
+ // Replace the string-switch instruction by a goto instruction.
+ block.exit().replace(new Goto(newBlock), code);
+ block.link(newBlock);
+ } else {
+ // Set the fallthrough block for the previously added if-instruction.
+ previous.link(newBlock);
+ }
+ previous = newBlock;
+ }
+ assert previous != null;
+ // Set the fallthrough block for the last if-instruction.
+ previous.link(fallthroughBlock);
+ }
+ }
+
+ private class SingleHashBasedStringSwitchRemover extends SingleStringSwitchRemover {
+
+ private final BasicBlock hashSwitchBlock;
+ private final BasicBlock hashSwitchFallthroughBlock;
+ private final BasicBlock idSwitchBlock;
+ private final BasicBlock idSwitchFallthroughBlock;
+
+ Int2ReferenceMap<Map<DexString, BasicBlock>> structure;
+
+ private int nextBlockNumber;
+ private int nextStringId;
+
+ private SingleHashBasedStringSwitchRemover(
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ BasicBlock hashSwitchBlock,
+ StringSwitch theSwitch,
+ Set<BasicBlock> newBlocksWithStrings)
+ throws UTFDataFormatException {
+ super(code, blockIterator, theSwitch, newBlocksWithStrings);
+ this.hashSwitchBlock = hashSwitchBlock;
+ this.hashSwitchFallthroughBlock = theSwitch.fallthroughBlock();
+ this.idSwitchBlock = theSwitch.fallthroughBlock().getUniqueNormalSuccessor();
+ this.idSwitchFallthroughBlock = idSwitchBlock.getUniqueNormalSuccessor();
+ this.structure = createStructure(theSwitch);
+ this.nextBlockNumber = code.getHighestBlockNumber() + 1;
+ }
+
+ private int getAndIncrementNextBlockNumber() {
+ return nextBlockNumber++;
+ }
+
+ private Int2ReferenceMap<Map<DexString, BasicBlock>> createStructure(StringSwitch theSwitch)
+ throws UTFDataFormatException {
+ Int2ReferenceMap<Map<DexString, BasicBlock>> result = new Int2ReferenceRBTreeMap<>();
+ theSwitch.forEachCase(
+ (key, target) -> {
+ int hashCode = key.decodedHashCode();
+ if (result.containsKey(hashCode)) {
+ result.get(hashCode).put(key, target);
+ } else {
+ Map<DexString, BasicBlock> cases = new LinkedHashMap<>();
+ cases.put(key, target);
+ result.put(hashCode, cases);
+ }
+ });
+ return result;
+ }
+
+ @Override
+ void removeStringSwitch() {
+ // Remove outgoing control flow edges from the block containing the string switch.
+ for (BasicBlock successor : hashSwitchBlock.getNormalSuccessors()) {
+ successor.removePredecessor(hashSwitchBlock, null);
+ }
+ hashSwitchBlock.removeAllNormalSuccessors();
+
+ // 1. Insert `int id = -1`.
+ InstructionListIterator instructionIterator =
+ hashSwitchBlock.listIterator(code, hashSwitchBlock.size());
+ instructionIterator.previous();
+
+ Phi idPhi = code.createPhi(idSwitchBlock, TypeElement.getInt());
+ Value notFoundIdValue =
+ instructionIterator.insertConstIntInstruction(code, appView.options(), -1);
+ idPhi.appendOperand(notFoundIdValue);
+
+ // 2. Insert `int hashCode = stringValue.hashCode()`.
+ InvokeVirtual hashInvoke =
+ new InvokeVirtual(
+ appView.dexItemFactory().stringMembers.hashCode,
+ code.createValue(TypeElement.getInt()),
+ ImmutableList.of(stringValue));
+ hashInvoke.setPosition(position);
+ instructionIterator.add(hashInvoke);
+
+ // 3. Create all the target blocks of the hash switch.
+ //
+ // Say that the string switch instruction contains the keys "X" and "Y" and assume that "X"
+ // and "Y" have the same hash code. Then this will create code that looks like:
+ //
+ // Block N:
+ // boolean equalsX = stringValue.equals("X")
+ // if equalsX then goto block N+1 else goto block N+2
+ // Block N+1:
+ // id = 0
+ // goto <id-switch-block>
+ // Block N+2:
+ // boolean equalsY = stringValue.equals("Y")
+ // if equalsY then goto block N+3 else goto block N+4
+ // Block N+3:
+ // id = 1
+ // goto <id-switch-block>
+ // Block N+4:
+ // goto <id-switch-block>
+ createHashSwitchTargets(idPhi, notFoundIdValue);
+ hashSwitchBlock.link(hashSwitchFallthroughBlock);
+
+ // 4. Insert `switch (hashValue)`.
+ IntSwitch hashSwitch = createHashSwitch(hashInvoke.outValue());
+ instructionIterator.next();
+ instructionIterator.replaceCurrentInstruction(hashSwitch);
+
+ // 5. Link `idSwitchBlock` with all of its target blocks.
+ Reference2IntMap<BasicBlock> targetBlockIndices = new Reference2IntOpenHashMap<>();
+ targetBlockIndices.defaultReturnValue(-1);
+ idSwitchBlock.getMutableSuccessors().clear();
+ for (Map<DexString, BasicBlock> cases : structure.values()) {
+ for (BasicBlock target : cases.values()) {
+ int targetIndex = targetBlockIndices.getInt(target);
+ if (targetIndex == -1) {
+ targetBlockIndices.put(target, idSwitchBlock.getSuccessors().size());
+ idSwitchBlock.link(target);
+ }
+ }
+ }
+ idSwitchBlock.getMutableSuccessors().add(idSwitchFallthroughBlock);
+
+ // 6. Insert `switch (idValue)`.
+ IntSwitch idSwitch = createIdSwitch(idPhi, targetBlockIndices);
+ InstructionListIterator idSwitchBlockInstructionIterator = idSwitchBlock.listIterator(code);
+ idSwitchBlockInstructionIterator.next();
+ idSwitchBlockInstructionIterator.replaceCurrentInstruction(idSwitch);
+ }
+
+ private IntSwitch createHashSwitch(Value hashValue) {
+ int[] hashSwitchKeys = structure.keySet().toArray(new int[0]);
+ int[] hashSwitchTargetIndices = new int[hashSwitchKeys.length];
+ for (int i = 0, offset = hashSwitchBlock.numberOfExceptionalSuccessors();
+ i < hashSwitchTargetIndices.length;
+ i++) {
+ hashSwitchTargetIndices[i] = i + offset;
+ }
+ int hashSwitchFallthroughIndex = hashSwitchBlock.getSuccessors().size() - 1;
+ return new IntSwitch(
+ hashValue, hashSwitchKeys, hashSwitchTargetIndices, hashSwitchFallthroughIndex);
+ }
+
+ private void createHashSwitchTargets(Phi idPhi, Value notFoundIdValue) {
+ for (Map<DexString, BasicBlock> cases : structure.values()) {
+ // Create the target block for the hash switch.
+ BasicBlock hashBlock =
+ BasicBlock.createGotoBlock(getAndIncrementNextBlockNumber(), position, code.metadata());
+ blockIterator.add(hashBlock);
+ hashSwitchBlock.link(hashBlock);
+
+ // Position the block iterator at the newly created block.
+ BasicBlock previous = blockIterator.previous();
+ assert previous == hashBlock;
+ blockIterator.next();
+
+ BasicBlock current = hashBlock;
+ for (Entry<DexString, BasicBlock> entry : cases.entrySet()) {
+ current.getMutableSuccessors().clear();
+
+ // Insert `String key = <entry.getKey()>`.
+ InstructionListIterator instructionIterator = current.listIterator(code);
+ Value keyValue =
+ instructionIterator.insertConstStringInstruction(appView, code, entry.getKey());
+ newBlocksWithStrings.add(current);
+
+ // Insert `boolean equalsKey = stringValue.equals(key)`.
+ InvokeVirtual equalsInvoke =
+ new InvokeVirtual(
+ appView.dexItemFactory().stringMembers.equals,
+ code.createValue(TypeElement.getInt()),
+ ImmutableList.of(stringValue, keyValue));
+ equalsInvoke.setPosition(position);
+ instructionIterator.add(equalsInvoke);
+
+ // Create a new block for the success case.
+ BasicBlock equalsKeyBlock =
+ BasicBlock.createGotoBlock(
+ getAndIncrementNextBlockNumber(), position, code.metadata(), idSwitchBlock);
+ idSwitchBlock.getMutablePredecessors().add(equalsKeyBlock);
+ blockIterator.add(equalsKeyBlock);
+ current.link(equalsKeyBlock);
+
+ // Insert `int id = <nextStringId++>`.
+ Value idValue =
+ equalsKeyBlock
+ .listIterator(code)
+ .insertConstIntInstruction(code, appView.options(), nextStringId++);
+ idPhi.appendOperand(idValue);
+
+ // Create a new block for the failure case.
+ BasicBlock continuationBlock =
+ BasicBlock.createGotoBlock(
+ getAndIncrementNextBlockNumber(), position, code.metadata(), idSwitchBlock);
+ blockIterator.add(continuationBlock);
+ current.link(continuationBlock);
+
+ // Insert `if (equalsKey) goto <id-switch-block> else goto <continuation-block>`.
+ instructionIterator.next();
+ instructionIterator.replaceCurrentInstruction(new If(Type.NE, equalsInvoke.outValue()));
+
+ current = continuationBlock;
+ }
+ idPhi.appendOperand(notFoundIdValue);
+ idSwitchBlock.getMutablePredecessors().add(current);
+ }
+ }
+
+ private IntSwitch createIdSwitch(Phi idPhi, Reference2IntMap<BasicBlock> targetBlockIndices) {
+ int numberOfCases = nextStringId;
+ int[] keys = ArrayUtils.createIdentityArray(numberOfCases);
+ int[] targetIndices = new int[numberOfCases];
+ int i = 0;
+ for (Map<DexString, BasicBlock> cases : structure.values()) {
+ for (Entry<DexString, BasicBlock> entry : cases.entrySet()) {
+ targetIndices[i++] = targetBlockIndices.getInt(entry.getValue());
+ }
+ }
+ int fallthroughIndex = targetBlockIndices.size();
+ return new IntSwitch(idPhi, keys, targetIndices, fallthroughIndex);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 11180c4..12e4c5e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -368,7 +368,8 @@
DexType vivifiedType =
appView
.dexItemFactory()
- .createType(DescriptorUtils.javaTypeToDescriptor(VIVIFIED_PREFIX + type.toString()));
+ .createSynthesizedType(
+ DescriptorUtils.javaTypeToDescriptor(VIVIFIED_PREFIX + type.toString()));
appView.rewritePrefix.rewriteType(vivifiedType, type);
return vivifiedType;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 0e52152..64825c1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -484,6 +484,6 @@
+ '$'
+ suffix
+ ';';
- return appView.dexItemFactory().createType(descriptor);
+ return appView.dexItemFactory().createSynthesizedType(descriptor);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 28ac0fd..9bdc9b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -709,7 +709,7 @@
assert type.isClassType();
String descriptor = type.descriptor.toString();
String elTypeDescriptor = getEmulateLibraryInterfaceClassDescriptor(descriptor);
- return factory.createType(elTypeDescriptor);
+ return factory.createSynthesizedType(elTypeDescriptor);
}
private void reportStaticInterfaceMethodHandle(DexMethod referencedFrom, DexMethodHandle handle) {
@@ -737,7 +737,7 @@
assert type.isClassType();
String descriptor = type.descriptor.toString();
String ccTypeDescriptor = getCompanionClassDescriptor(descriptor);
- return factory.createType(ccTypeDescriptor);
+ return factory.createSynthesizedType(ccTypeDescriptor);
}
DexType getCompanionClassType(DexType type) {
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 9f34126..eb36e39 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
@@ -948,7 +948,7 @@
}
public boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
- if (!code.metadata().mayHaveIntSwitch()) {
+ if (!code.metadata().mayHaveSwitch()) {
return false;
}
@@ -959,8 +959,8 @@
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
- if (instruction.isIntSwitch()) {
- IntSwitch theSwitch = instruction.asIntSwitch();
+ if (instruction.isSwitch()) {
+ Switch theSwitch = instruction.asSwitch();
if (options.testing.enableDeadSwitchCaseElimination) {
SwitchCaseEliminator eliminator =
removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer);
@@ -975,119 +975,12 @@
continue;
}
- assert instruction.isIntSwitch();
- theSwitch = instruction.asIntSwitch();
+ assert instruction.isSwitch();
+ theSwitch = instruction.asSwitch();
}
}
- if (theSwitch.numberOfKeys() == 1) {
- // Rewrite the switch to an if.
- int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
- int caseBlockIndex = theSwitch.targetBlockIndices()[0];
- if (fallthroughBlockIndex < caseBlockIndex) {
- block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
- }
- if (theSwitch.getFirstKey() == 0) {
- iterator.replaceCurrentInstruction(new If(Type.EQ, theSwitch.value()));
- } else {
- ConstNumber labelConst = code.createIntConstant(theSwitch.getFirstKey());
- labelConst.setPosition(theSwitch.getPosition());
- iterator.previous();
- iterator.add(labelConst);
- Instruction dummy = iterator.next();
- assert dummy == theSwitch;
- If theIf = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.dest()));
- iterator.replaceCurrentInstruction(theIf);
- }
- } else {
- // If there are more than 1 key, we use the following algorithm to find keys to combine.
- // First, scan through the keys forward and combine each packed interval with the
- // previous interval if it gives a net saving.
- // Secondly, go through all created intervals and combine the ones without a saving into
- // a single interval and keep a max number of packed switches.
- // Finally, go through all intervals and check if the switch or part of the switch
- // should be transformed to ifs.
-
- // Phase 1: Combine packed intervals.
- InternalOutputMode mode = options.getInternalOutputMode();
- int[] keys = theSwitch.getKeys();
- int maxNumberOfIfsOrSwitches = 10;
- PriorityQueue<Interval> biggestPackedSavings =
- new PriorityQueue<>(
- (x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode)));
- Set<Interval> biggestPackedSet = new HashSet<>();
- List<Interval> intervals = new ArrayList<>();
- int previousKey = keys[0];
- IntList currentKeys = new IntArrayList();
- currentKeys.add(previousKey);
- Interval previousInterval = null;
- for (int i = 1; i < keys.length; i++) {
- int key = keys[i];
- if (((long) key - (long) previousKey) > 1) {
- Interval current = new Interval(currentKeys);
- Interval added = combineOrAddInterval(intervals, previousInterval, current);
- if (added != current && biggestPackedSet.contains(previousInterval)) {
- biggestPackedSet.remove(previousInterval);
- biggestPackedSavings.remove(previousInterval);
- }
- tryAddToBiggestSavings(
- biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
- previousInterval = added;
- currentKeys = new IntArrayList();
- }
- currentKeys.add(key);
- previousKey = key;
- }
- Interval current = new Interval(currentKeys);
- Interval added = combineOrAddInterval(intervals, previousInterval, current);
- if (added != current && biggestPackedSet.contains(previousInterval)) {
- biggestPackedSet.remove(previousInterval);
- biggestPackedSavings.remove(previousInterval);
- }
- tryAddToBiggestSavings(
- biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
-
- // Phase 2: combine sparse intervals into a single bin.
- // Check if we should save a space for a sparse switch, if so, remove the switch with
- // the smallest savings.
- if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches
- && maxNumberOfIfsOrSwitches < intervals.size()) {
- biggestPackedSet.remove(biggestPackedSavings.poll());
- }
- Interval sparse = null;
- List<Interval> newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches);
- for (int i = 0; i < intervals.size(); i++) {
- Interval interval = intervals.get(i);
- if (biggestPackedSet.contains(interval)) {
- newSwitches.add(interval);
- } else if (sparse == null) {
- sparse = interval;
- newSwitches.add(sparse);
- } else {
- sparse.addInterval(interval);
- }
- }
-
- // Phase 3: at this point we are guaranteed to have the biggest saving switches
- // in newIntervals, potentially with a switch combining the remaining intervals.
- // Now we check to see if we can create any if's to reduce size.
- IntList outliers = new IntArrayList();
- int outliersAsIfSize =
- appView.options().testing.enableSwitchToIfRewriting
- ? findIfsForCandidates(newSwitches, theSwitch, outliers)
- : 0;
-
- long newSwitchesSize = 0;
- List<IntList> newSwitchSequences = new ArrayList<>(newSwitches.size());
- for (Interval interval : newSwitches) {
- newSwitchesSize += interval.estimatedSize(mode);
- newSwitchSequences.add(interval.keys);
- }
-
- long currentSize = IntSwitch.estimatedSize(mode, theSwitch.getKeys());
- if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) {
- convertSwitchToSwitchAndIfs(
- code, blocksIterator, block, iterator, theSwitch, newSwitchSequences, outliers);
- }
+ if (theSwitch.isIntSwitch()) {
+ rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch());
}
}
}
@@ -1107,9 +1000,127 @@
return !affectedValues.isEmpty();
}
+ private void rewriteIntSwitch(
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ BasicBlock block,
+ InstructionListIterator iterator,
+ IntSwitch theSwitch) {
+ if (theSwitch.numberOfKeys() == 1) {
+ // Rewrite the switch to an if.
+ int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
+ int caseBlockIndex = theSwitch.targetBlockIndices()[0];
+ if (fallthroughBlockIndex < caseBlockIndex) {
+ block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
+ }
+ If replacement;
+ if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) {
+ replacement = new If(Type.EQ, theSwitch.value());
+ } else {
+ Instruction labelConst = theSwitch.materializeFirstKey(appView, code);
+ labelConst.setPosition(theSwitch.getPosition());
+ iterator.previous();
+ iterator.add(labelConst);
+ Instruction dummy = iterator.next();
+ assert dummy == theSwitch;
+ replacement = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue()));
+ }
+ iterator.replaceCurrentInstruction(replacement);
+ return;
+ }
+
+ // If there are more than 1 key, we use the following algorithm to find keys to combine.
+ // First, scan through the keys forward and combine each packed interval with the
+ // previous interval if it gives a net saving.
+ // Secondly, go through all created intervals and combine the ones without a saving into
+ // a single interval and keep a max number of packed switches.
+ // Finally, go through all intervals and check if the switch or part of the switch
+ // should be transformed to ifs.
+
+ // Phase 1: Combine packed intervals.
+ InternalOutputMode mode = options.getInternalOutputMode();
+ int[] keys = theSwitch.getKeys();
+ int maxNumberOfIfsOrSwitches = 10;
+ PriorityQueue<Interval> biggestPackedSavings =
+ new PriorityQueue<>((x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode)));
+ Set<Interval> biggestPackedSet = new HashSet<>();
+ List<Interval> intervals = new ArrayList<>();
+ int previousKey = keys[0];
+ IntList currentKeys = new IntArrayList();
+ currentKeys.add(previousKey);
+ Interval previousInterval = null;
+ for (int i = 1; i < keys.length; i++) {
+ int key = keys[i];
+ if (((long) key - (long) previousKey) > 1) {
+ Interval current = new Interval(currentKeys);
+ Interval added = combineOrAddInterval(intervals, previousInterval, current);
+ if (added != current && biggestPackedSet.contains(previousInterval)) {
+ biggestPackedSet.remove(previousInterval);
+ biggestPackedSavings.remove(previousInterval);
+ }
+ tryAddToBiggestSavings(
+ biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+ previousInterval = added;
+ currentKeys = new IntArrayList();
+ }
+ currentKeys.add(key);
+ previousKey = key;
+ }
+ Interval current = new Interval(currentKeys);
+ Interval added = combineOrAddInterval(intervals, previousInterval, current);
+ if (added != current && biggestPackedSet.contains(previousInterval)) {
+ biggestPackedSet.remove(previousInterval);
+ biggestPackedSavings.remove(previousInterval);
+ }
+ tryAddToBiggestSavings(biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+
+ // Phase 2: combine sparse intervals into a single bin.
+ // Check if we should save a space for a sparse switch, if so, remove the switch with
+ // the smallest savings.
+ if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches
+ && maxNumberOfIfsOrSwitches < intervals.size()) {
+ biggestPackedSet.remove(biggestPackedSavings.poll());
+ }
+ Interval sparse = null;
+ List<Interval> newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches);
+ for (int i = 0; i < intervals.size(); i++) {
+ Interval interval = intervals.get(i);
+ if (biggestPackedSet.contains(interval)) {
+ newSwitches.add(interval);
+ } else if (sparse == null) {
+ sparse = interval;
+ newSwitches.add(sparse);
+ } else {
+ sparse.addInterval(interval);
+ }
+ }
+
+ // Phase 3: at this point we are guaranteed to have the biggest saving switches
+ // in newIntervals, potentially with a switch combining the remaining intervals.
+ // Now we check to see if we can create any if's to reduce size.
+ IntList outliers = new IntArrayList();
+ int outliersAsIfSize =
+ appView.options().testing.enableSwitchToIfRewriting
+ ? findIfsForCandidates(newSwitches, theSwitch, outliers)
+ : 0;
+
+ long newSwitchesSize = 0;
+ List<IntList> newSwitchSequences = new ArrayList<>(newSwitches.size());
+ for (Interval interval : newSwitches) {
+ newSwitchesSize += interval.estimatedSize(mode);
+ newSwitchSequences.add(interval.keys);
+ }
+
+ long currentSize = IntSwitch.estimatedSize(mode, theSwitch.getKeys());
+ if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) {
+ convertSwitchToSwitchAndIfs(
+ code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers);
+ }
+ }
+
private SwitchCaseEliminator removeUnnecessarySwitchCases(
IRCode code,
- IntSwitch theSwitch,
+ Switch theSwitch,
InstructionListIterator iterator,
SwitchCaseAnalyzer switchCaseAnalyzer) {
BasicBlock defaultTarget = theSwitch.fallthroughBlock();
@@ -1118,9 +1129,18 @@
new BasicBlockBehavioralSubsumption(appView, code.method.holder());
// Compute the set of switch cases that can be removed.
+ int alwaysHitCase = -1;
for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
BasicBlock targetBlock = theSwitch.targetBlock(i);
+ if (switchCaseAnalyzer.switchCaseIsAlwaysHit(theSwitch, i)) {
+ if (eliminator == null) {
+ eliminator = new SwitchCaseEliminator(theSwitch, iterator);
+ }
+ eliminator.markSwitchCaseAsAlwaysHit(i);
+ break;
+ }
+
// This switch case can be removed if the behavior of the target block is equivalent to the
// behavior of the default block, or if the switch case is unreachable.
if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, i)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index eef169e..64a8c86 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -189,7 +189,7 @@
}
// Make sure the (base) type is resolvable.
DexType baseType = type.toBaseType(dexItemFactory);
- DexClass baseClazz = appView.definitionFor(baseType);
+ DexClass baseClazz = appView.appInfo().definitionForWithoutExistenceAssert(baseType);
if (baseClazz == null || !baseClazz.isResolvable(appView)) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index d626168..3285e2a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -36,6 +36,7 @@
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
/**
* ServiceLoaderRewriter will attempt to rewrite calls on the form of: ServiceLoader.load(X.class,
@@ -69,7 +70,7 @@
public static final String SERVICE_LOADER_CLASS_NAME = "$$ServiceLoaderMethods";
private static final String SERVICE_LOADER_METHOD_PREFIX_NAME = "$load";
- private DexProgramClass synthesizedClass;
+ private AtomicReference<DexProgramClass> synthesizedClass = new AtomicReference<>();
private ConcurrentHashMap<DexType, DexEncodedMethod> synthesizedServiceLoaders =
new ConcurrentHashMap<>();
@@ -82,7 +83,7 @@
}
public DexProgramClass getSynthesizedClass() {
- return synthesizedClass;
+ return synthesizedClass.get();
}
public void rewrite(IRCode code) {
@@ -184,40 +185,12 @@
}
private DexEncodedMethod createSynthesizedMethod(DexType serviceType, List<DexClass> classes) {
- DexType serviceLoaderType =
- appView.dexItemFactory().createType("L" + SERVICE_LOADER_CLASS_NAME + ";");
- if (synthesizedClass == null) {
- assert !appView.options().encodeChecksums;
- ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
- synthesizedClass =
- new DexProgramClass(
- serviceLoaderType,
- null,
- new SynthesizedOrigin("Service Loader desugaring", getClass()),
- ClassAccessFlags.fromDexAccessFlags(
- Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
- appView.dexItemFactory().objectType,
- DexTypeList.empty(),
- appView.dexItemFactory().createString("ServiceLoader"),
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- DexAnnotationSet.empty(),
- DexEncodedField.EMPTY_ARRAY, // Static fields.
- DexEncodedField.EMPTY_ARRAY, // Instance fields.
- DexEncodedMethod.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
- appView.dexItemFactory().getSkipNameValidationForTesting(),
- checksumSupplier);
- appView.appInfo().addSynthesizedClass(synthesizedClass);
- }
DexProto proto = appView.dexItemFactory().createProto(appView.dexItemFactory().iteratorType);
DexMethod method =
appView
.dexItemFactory()
.createMethod(
- serviceLoaderType,
+ appView.dexItemFactory().serviceLoaderRewrittenClassType,
proto,
SERVICE_LOADER_METHOD_PREFIX_NAME + atomicInteger.incrementAndGet());
MethodAccessFlags methodAccess =
@@ -230,10 +203,48 @@
ParameterAnnotationsList.empty(),
ServiceLoaderSourceCode.generate(serviceType, classes, appView.dexItemFactory()),
true);
- synthesizedClass.addDirectMethod(encodedMethod);
+ getOrSetSynthesizedClass().addDirectMethod(encodedMethod);
return encodedMethod;
}
+ private DexProgramClass getOrSetSynthesizedClass() {
+ if (synthesizedClass.get() != null) {
+ return synthesizedClass.get();
+ }
+ assert !appView.options().encodeChecksums;
+ ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
+ DexProgramClass clazz =
+ synthesizedClass.updateAndGet(
+ existingClazz -> {
+ if (existingClazz != null) {
+ return existingClazz;
+ }
+ return new DexProgramClass(
+ appView.dexItemFactory().serviceLoaderRewrittenClassType,
+ null,
+ new SynthesizedOrigin("Service Loader desugaring", getClass()),
+ ClassAccessFlags.fromDexAccessFlags(
+ Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
+ appView.dexItemFactory().objectType,
+ DexTypeList.empty(),
+ appView.dexItemFactory().createString("ServiceLoader"),
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY, // Static fields.
+ DexEncodedField.EMPTY_ARRAY, // Instance fields.
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
+ appView.dexItemFactory().getSkipNameValidationForTesting(),
+ checksumSupplier);
+ });
+ assert clazz != null;
+ appView.appInfo().addSynthesizedClass(clazz);
+ return clazz;
+ }
+
/**
* Rewriter assumes that the code is of the form:
*
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
index 0aabe6e..733db77 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
@@ -4,10 +4,13 @@
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.StringSwitch;
+import com.android.tools.r8.ir.code.Switch;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
@@ -21,12 +24,14 @@
private final BasicBlock block;
private final BasicBlock defaultTarget;
private final InstructionListIterator iterator;
- private final IntSwitch theSwitch;
+ private final Switch theSwitch;
+ private int alwaysHitCase = -1;
+ private BasicBlock alwaysHitTarget;
private boolean mayHaveIntroducedUnreachableBlocks = false;
private IntSet switchCasesToBeRemoved;
- SwitchCaseEliminator(IntSwitch theSwitch, InstructionListIterator iterator) {
+ SwitchCaseEliminator(Switch theSwitch, InstructionListIterator iterator) {
this.block = theSwitch.getBlock();
this.defaultTarget = theSwitch.fallthroughBlock();
this.iterator = iterator;
@@ -40,13 +45,34 @@
private boolean canBeOptimized() {
assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty();
- return switchCasesToBeRemoved != null;
+ return switchCasesToBeRemoved != null || hasAlwaysHitCase();
}
boolean mayHaveIntroducedUnreachableBlocks() {
return mayHaveIntroducedUnreachableBlocks;
}
+ public boolean isSwitchCaseLive(int index) {
+ if (hasAlwaysHitCase()) {
+ return index == alwaysHitCase;
+ }
+ return !switchCasesToBeRemoved.contains(index);
+ }
+
+ public boolean isFallthroughLive() {
+ return !hasAlwaysHitCase();
+ }
+
+ public boolean hasAlwaysHitCase() {
+ return alwaysHitCase >= 0;
+ }
+
+ void markSwitchCaseAsAlwaysHit(int i) {
+ assert alwaysHitCase < 0;
+ alwaysHitCase = i;
+ alwaysHitTarget = theSwitch.targetBlock(i);
+ }
+
void markSwitchCaseForRemoval(int i) {
if (switchCasesToBeRemoved == null) {
switchCasesToBeRemoved = new IntOpenHashSet();
@@ -58,8 +84,8 @@
if (canBeOptimized()) {
int originalNumberOfSuccessors = block.getSuccessors().size();
unlinkDeadSuccessors();
- if (allSwitchCasesMarkedForRemoval()) {
- // Replace switch with a simple goto since only the fall through is left.
+ if (hasAlwaysHitCase() || allSwitchCasesMarkedForRemoval()) {
+ // Replace switch with a simple goto.
replaceSwitchByGoto();
} else {
// Replace switch by a new switch where the dead switch cases have been removed.
@@ -90,12 +116,14 @@
private IntPredicate computeSuccessorHasBecomeDeadPredicate() {
int[] numberOfControlFlowEdgesToBlockWithIndex = new int[block.getSuccessors().size()];
for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
- if (!switchCasesToBeRemoved.contains(i)) {
+ if (isSwitchCaseLive(i)) {
int targetBlockIndex = theSwitch.getTargetBlockIndex(i);
numberOfControlFlowEdgesToBlockWithIndex[targetBlockIndex] += 1;
}
}
- numberOfControlFlowEdgesToBlockWithIndex[theSwitch.getFallthroughBlockIndex()] += 1;
+ if (isFallthroughLive()) {
+ numberOfControlFlowEdgesToBlockWithIndex[theSwitch.getFallthroughBlockIndex()] += 1;
+ }
for (int i : block.getCatchHandlersWithSuccessorIndexes().getUniqueTargets()) {
numberOfControlFlowEdgesToBlockWithIndex[i] += 1;
}
@@ -103,7 +131,9 @@
}
private void replaceSwitchByGoto() {
- iterator.replaceCurrentInstruction(new Goto(defaultTarget));
+ assert !hasAlwaysHitCase() || alwaysHitTarget != null;
+ BasicBlock target = hasAlwaysHitCase() ? alwaysHitTarget : defaultTarget;
+ iterator.replaceCurrentInstruction(new Goto(target));
}
private void replaceSwitchByOptimizedSwitch(int originalNumberOfSuccessors) {
@@ -121,11 +151,9 @@
}
int newNumberOfKeys = theSwitch.numberOfKeys() - switchCasesToBeRemoved.size();
- int[] newKeys = new int[newNumberOfKeys];
int[] newTargetBlockIndices = new int[newNumberOfKeys];
for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
if (!switchCasesToBeRemoved.contains(i)) {
- newKeys[j] = theSwitch.getKey(i);
newTargetBlockIndices[j] =
theSwitch.getTargetBlockIndex(i)
- targetBlockIndexOffset[theSwitch.getTargetBlockIndex(i)];
@@ -135,12 +163,41 @@
}
}
- iterator.replaceCurrentInstruction(
- new IntSwitch(
- theSwitch.value(),
- newKeys,
- newTargetBlockIndices,
- theSwitch.getFallthroughBlockIndex()
- - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]));
+ Switch replacement;
+ if (theSwitch.isIntSwitch()) {
+ IntSwitch intSwitch = theSwitch.asIntSwitch();
+ int[] newKeys = new int[newNumberOfKeys];
+ for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
+ if (!switchCasesToBeRemoved.contains(i)) {
+ newKeys[j] = intSwitch.getKey(i);
+ j++;
+ }
+ }
+ replacement =
+ new IntSwitch(
+ theSwitch.value(),
+ newKeys,
+ newTargetBlockIndices,
+ theSwitch.getFallthroughBlockIndex()
+ - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]);
+ } else {
+ assert theSwitch.isStringSwitch();
+ StringSwitch stringSwitch = theSwitch.asStringSwitch();
+ DexString[] newKeys = new DexString[newNumberOfKeys];
+ for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
+ if (!switchCasesToBeRemoved.contains(i)) {
+ newKeys[j] = stringSwitch.getKey(i);
+ j++;
+ }
+ }
+ replacement =
+ new StringSwitch(
+ theSwitch.value(),
+ newKeys,
+ newTargetBlockIndices,
+ theSwitch.getFallthroughBlockIndex()
+ - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]);
+ }
+ iterator.replaceCurrentInstruction(replacement);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
index 6c22a72..76f63ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.ir.optimize.controlflow;
-import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.LongInterval;
public class SwitchCaseAnalyzer {
@@ -17,9 +20,35 @@
return INSTANCE;
}
- public boolean switchCaseIsUnreachable(IntSwitch theSwitch, int index) {
+ public boolean switchCaseIsAlwaysHit(Switch theSwitch, int index) {
Value switchValue = theSwitch.value();
- return switchValue.hasValueRange()
- && !switchValue.getValueRange().containsValue(theSwitch.getKey(index));
+ if (theSwitch.isIntSwitch()) {
+ LongInterval valueRange = switchValue.getValueRange();
+ return valueRange != null
+ && valueRange.isSingleValue()
+ && valueRange.containsValue(theSwitch.asIntSwitch().getKey(index));
+ }
+
+ assert theSwitch.isStringSwitch();
+
+ Value rootSwitchValue = switchValue.getAliasedValue();
+ DexString key = theSwitch.asStringSwitch().getKey(index);
+ return rootSwitchValue.isDefinedByInstructionSatisfying(Instruction::isConstString)
+ && key == rootSwitchValue.definition.asConstString().getValue();
+ }
+
+ public boolean switchCaseIsUnreachable(Switch theSwitch, int index) {
+ Value switchValue = theSwitch.value();
+ if (theSwitch.isIntSwitch()) {
+ return switchValue.hasValueRange()
+ && !switchValue.getValueRange().containsValue(theSwitch.asIntSwitch().getKey(index));
+ }
+
+ assert theSwitch.isStringSwitch();
+
+ Value rootSwitchValue = switchValue.getAliasedValue();
+ DexString key = theSwitch.asStringSwitch().getKey(index);
+ return rootSwitchValue.isDefinedByInstructionSatisfying(Instruction::isConstString)
+ && key != rootSwitchValue.definition.asConstString().getValue();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 0a35a6f..61a7c13 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -138,6 +138,14 @@
if (outValue.getType().isNullType()) {
addNullDependencies(outValue.uniqueUsers(), eligibleEnums);
}
+ } else {
+ if (instruction.isInvokeMethod()) {
+ DexProgramClass enumClass =
+ getEnumUnboxingCandidateOrNull(instruction.asInvokeMethod().getReturnType());
+ if (enumClass != null) {
+ eligibleEnums.add(enumClass.type);
+ }
+ }
}
if (instruction.isConstClass()) {
analyzeConstClass(instruction.asConstClass(), eligibleEnums);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 0b39506..1bda5d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -151,8 +151,9 @@
}
private void removePinnedIfNotHolder(DexMember<?, ?> member, DexType type) {
- if (type != member.holder) {
- removePinnedCandidate(type);
+ DexType baseType = type.toBaseType(factory);
+ if (baseType != member.holder) {
+ removePinnedCandidate(baseType);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java
index 386a32d..953514d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java
@@ -16,8 +16,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.IdentityHashMap;
-import java.util.Map;
+import java.util.LinkedHashMap;
/**
* Extracts the ordinal values and any anonymous subtypes for all Enum classes from their static
@@ -57,12 +56,8 @@
}
DexEncodedMethod initializer = clazz.getClassInitializer();
IRCode code = initializer.getCode().buildIR(initializer, appView, clazz.origin);
- Map<DexField, EnumValueInfo> enumValueInfoMap = new IdentityHashMap<>();
- for (Instruction insn : code.instructions()) {
- if (!insn.isStaticPut()) {
- continue;
- }
- StaticPut staticPut = insn.asStaticPut();
+ LinkedHashMap<DexField, EnumValueInfo> enumValueInfoMap = new LinkedHashMap<>();
+ for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
if (staticPut.getField().type != clazz.type) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 30766c5..97d0029 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -26,15 +27,21 @@
import com.android.tools.r8.ir.code.IntSwitch;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.SwitchMapCollector;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.Arrays;
+import java.util.Set;
public class EnumValueOptimizer {
@@ -157,36 +164,92 @@
* </blockquote>
*/
public void removeSwitchMaps(IRCode code) {
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ boolean mayHaveIntroducedUnreachableBlocks = false;
for (BasicBlock block : code.blocks) {
- JumpInstruction exit = block.exit();
+ IntSwitch switchInsn = block.exit().asIntSwitch();
// Pattern match a switch on a switch map as input.
- if (!exit.isIntSwitch()) {
+ if (switchInsn == null) {
continue;
}
- IntSwitch switchInsn = exit.asIntSwitch();
+
EnumSwitchInfo info = analyzeSwitchOverEnum(switchInsn);
if (info == null) {
continue;
}
- Int2IntMap targetMap = new Int2IntArrayMap();
+
+ Int2IntMap ordinalToTargetMap = new Int2IntArrayMap(switchInsn.numberOfKeys());
for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
DexField field = info.indexMap.get(switchInsn.getKey(i));
EnumValueInfo valueInfo = info.valueInfoMap.getEnumValueInfo(field);
- targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
+ if (valueInfo != null) {
+ ordinalToTargetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
+ } else {
+ // The switch map refers to a field on the enum that does not exist in this compilation.
+ }
}
- int[] keys = targetMap.keySet().toIntArray();
+
+ int fallthroughBlockIndex = switchInsn.getFallthroughBlockIndex();
+ if (ordinalToTargetMap.size() < switchInsn.numberOfKeys()) {
+ // There is at least one dead switch case. This can happen when some dependencies use
+ // different versions of the same enum.
+ int numberOfNormalSuccessors = switchInsn.numberOfKeys() + 1;
+ int numberOfExceptionalSuccessors = block.numberOfExceptionalSuccessors();
+ IntSet ordinalToTargetValues = new IntOpenHashSet(ordinalToTargetMap.values());
+
+ // Compute which successors that are dead. We don't include the exceptional successors,
+ // since none of them are dead. Therefore, `deadBlockIndices[i]` represents if the i'th
+ // normal successor is dead, i.e., if the (i+numberOfExceptionalSuccessors)'th successor is
+ // dead.
+ //
+ // Note: we use an int[] to efficiently fixup `ordinalToTargetMap` below.
+ int[] deadBlockIndices =
+ ArrayUtils.fromPredicate(
+ index -> {
+ // It is dead if it is not targeted by a switch case and it is not the fallthrough
+ // block.
+ int adjustedIndex = index + numberOfExceptionalSuccessors;
+ return !ordinalToTargetValues.contains(adjustedIndex)
+ && adjustedIndex != switchInsn.getFallthroughBlockIndex();
+ },
+ numberOfNormalSuccessors);
+
+ // Detach the dead successors from the graph, and record that we need to remove unreachable
+ // blocks in the end.
+ IntList successorIndicesToRemove = new IntArrayList(numberOfNormalSuccessors);
+ for (int i = 0; i < numberOfNormalSuccessors; i++) {
+ if (deadBlockIndices[i] == 1) {
+ BasicBlock successor = block.getSuccessors().get(i + numberOfExceptionalSuccessors);
+ successor.removePredecessor(block, affectedValues);
+ successorIndicesToRemove.add(i);
+ }
+ }
+ block.removeSuccessorsByIndex(successorIndicesToRemove);
+ mayHaveIntroducedUnreachableBlocks = true;
+
+ // Fixup `ordinalToTargetMap` and the fallthrough index.
+ ArrayUtils.sumOfPredecessorsInclusive(deadBlockIndices);
+ for (Int2IntMap.Entry entry : ordinalToTargetMap.int2IntEntrySet()) {
+ ordinalToTargetMap.put(
+ entry.getIntKey(), entry.getIntValue() - deadBlockIndices[entry.getIntValue()]);
+ }
+ fallthroughBlockIndex -= deadBlockIndices[fallthroughBlockIndex];
+ }
+
+ int[] keys = ordinalToTargetMap.keySet().toIntArray();
Arrays.sort(keys);
int[] targets = new int[keys.length];
for (int i = 0; i < keys.length; i++) {
- targets[i] = targetMap.get(keys[i]);
+ targets[i] = ordinalToTargetMap.get(keys[i]);
}
IntSwitch newSwitch =
- new IntSwitch(
- info.ordinalInvoke.outValue(), keys, targets, switchInsn.getFallthroughBlockIndex());
+ new IntSwitch(info.ordinalInvoke.outValue(), keys, targets, fallthroughBlockIndex);
+
// Replace the switch itself.
- exit.replace(newSwitch, code);
+ switchInsn.replace(newSwitch, code);
+
// If the original input to the switch is now unused, remove it too. It is not dead
// as it might have side-effects but we ignore these here.
Instruction arrayGet = info.arrayGet;
@@ -194,12 +257,19 @@
arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
arrayGet.getBlock().removeInstruction(arrayGet);
}
+
Instruction staticGet = info.staticGet;
if (!staticGet.outValue().hasUsers()) {
assert staticGet.inValues().isEmpty();
staticGet.getBlock().removeInstruction(staticGet);
}
}
+ if (mayHaveIntroducedUnreachableBlocks) {
+ affectedValues.addAll(code.removeUnreachableBlocks());
+ }
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
}
private static final class EnumSwitchInfo {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index d1ddce9..da135b0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -271,7 +271,7 @@
DexMethod invokedMethod = invoke.getInvokedMethod();
DexType returnType = invokedMethod.proto.returnType;
if (returnType.isClassType()
- && appView.appInfo().isRelatedBySubtyping(returnType, method.holder())) {
+ && appView.appInfo().inSameHierarchy(returnType, method.holder())) {
return; // Not allowed, could introduce an alias of the receiver.
}
callsReceiver.add(new Pair<>(Invoke.Type.VIRTUAL, invokedMethod));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
index 2683d2f..c718391 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
@@ -9,6 +9,8 @@
*/
public abstract class BridgeInfo {
+ public abstract boolean hasSameTarget(BridgeInfo bridgeInfo);
+
public boolean isVirtualBridgeInfo() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
index a07abdc..585699f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
@@ -38,6 +38,13 @@
}
@Override
+ public boolean hasSameTarget(BridgeInfo bridgeInfo) {
+ assert bridgeInfo.isVirtualBridgeInfo();
+ VirtualBridgeInfo virtualBridgeInfo = bridgeInfo.asVirtualBridgeInfo();
+ return invokedMethod.match(virtualBridgeInfo.invokedMethod);
+ }
+
+ @Override
public boolean isVirtualBridgeInfo() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index bed05c0..2779111 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -170,6 +170,9 @@
originalTypeInfo != null && originalTypeInfo.isObjectArray()
? originalTypeInfo.getArguments().get(0).variance
: KmVariance.INVARIANT;
+ if (variance == null) {
+ variance = KmVariance.INVARIANT;
+ }
renamedKmType.getArguments().add(new KmTypeProjection(variance, argumentType));
}
return renamedKmType;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index e9bc069..75d80b1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -70,7 +70,7 @@
}
public boolean isObjectArray() {
- if (!isClass()) {
+ if (isClass()) {
KmClassifier.Class classifier = (KmClassifier.Class) this.classifier;
return classifier.getName().equals(ClassClassifiers.arrayBinaryName) && arguments.size() == 1;
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index b2f5a80..ba73522 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -178,12 +178,13 @@
|| isClassNameValue(invoke.inValues().get(1), dexItemFactory));
}
- private static boolean isClassNameValue(Value value, DexItemFactory dexItemFactory) {
+ public static boolean isClassNameValue(Value value, DexItemFactory dexItemFactory) {
Value root = value.getAliasedValue();
- return !root.isPhi()
- && root.definition.isInvokeVirtual()
- && root.definition.asInvokeVirtual().getInvokedMethod()
- == dexItemFactory.classMethods.getName;
+ if (!root.isDefinedByInstructionSatisfying(Instruction::isInvokeVirtual)) {
+ return false;
+ }
+ InvokeVirtual invoke = root.definition.asInvokeVirtual();
+ return dexItemFactory.classMethods.isReflectiveNameLookup(invoke.getInvokedMethod());
}
/**
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
new file mode 100644
index 0000000..d3ca402
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -0,0 +1,268 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
+import com.android.tools.r8.ir.optimize.info.bridge.VirtualBridgeInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * An optimization pass that hoists bridges upwards with the purpose of sharing redundant bridge
+ * methods.
+ *
+ * <p>Example: <code>
+ * class A {
+ * void m() { ... }
+ * }
+ * class B1 extends A {
+ * void bridge() { m(); }
+ * }
+ * class B2 extends A {
+ * void bridge() { m(); }
+ * }
+ * </code> Is transformed into: <code>
+ * class A {
+ * void m() { ... }
+ * void bridge() { m(); }
+ * }
+ * class B1 extends A {}
+ * class B2 extends A {}
+ * </code>
+ */
+public class BridgeHoisting {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ // A lens that keeps track of the changes for construction of the Proguard map.
+ private final BridgeHoistingLens.Builder lensBuilder = new BridgeHoistingLens.Builder();
+
+ public BridgeHoisting(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ public void run() {
+ BottomUpClassHierarchyTraversal.forProgramClasses(appView)
+ .excludeInterfaces()
+ .visit(appView.appInfo().classes(), this::processClass);
+ if (!lensBuilder.isEmpty()) {
+ BridgeHoistingLens lens = lensBuilder.build(appView);
+ boolean changed = appView.setGraphLense(lens);
+ assert changed;
+ appView.setAppInfo(
+ appView.appInfo().rewrittenWithLens(appView.appInfo().app().asDirect(), lens));
+ }
+ }
+
+ private void processClass(DexProgramClass clazz) {
+ Set<DexType> subtypes = appView.appInfo().allImmediateSubtypes(clazz.type);
+ Set<DexProgramClass> subclasses = new TreeSet<>((x, y) -> x.type.slowCompareTo(y.type));
+ for (DexType subtype : subtypes) {
+ DexProgramClass subclass = asProgramClassOrNull(appView.definitionFor(subtype));
+ if (subclass == null) {
+ return;
+ }
+ subclasses.add(subclass);
+ }
+ for (Wrapper<DexMethod> candidate : getCandidatesForHoisting(subclasses)) {
+ hoistBridgeIfPossible(candidate.get(), clazz, subclasses);
+ }
+ }
+
+ private Set<Wrapper<DexMethod>> getCandidatesForHoisting(Set<DexProgramClass> subclasses) {
+ Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+ Set<Wrapper<DexMethod>> candidates = new HashSet<>();
+ for (DexProgramClass subclass : subclasses) {
+ for (DexEncodedMethod method : subclass.virtualMethods()) {
+ BridgeInfo bridgeInfo = method.getOptimizationInfo().getBridgeInfo();
+ // TODO(b/153147967): Even if the bridge is not targeting a method in the superclass, it may
+ // be possible to rewrite the bridge to target a method in the superclass, such that we can
+ // hoist it. Add a test.
+ if (bridgeInfo != null && bridgeIsTargetingMethodInSuperclass(subclass, bridgeInfo)) {
+ candidates.add(equivalence.wrap(method.method));
+ }
+ }
+ }
+ return candidates;
+ }
+
+ /**
+ * Returns true if the bridge method is referencing a method in the superclass of {@param holder}.
+ * If this is not the case, we cannot hoist the bridge method, as that would lead to a type error:
+ * <code>
+ * class A {
+ * void bridge() {
+ * v0 <- Argument
+ * invoke-virtual {v0}, void B.m() // <- not valid
+ * Return
+ * }
+ * }
+ * class B extends A {
+ * void m() {
+ * ...
+ * }
+ * }
+ * </code>
+ */
+ private boolean bridgeIsTargetingMethodInSuperclass(
+ DexProgramClass holder, BridgeInfo bridgeInfo) {
+ if (bridgeInfo.isVirtualBridgeInfo()) {
+ VirtualBridgeInfo virtualBridgeInfo = bridgeInfo.asVirtualBridgeInfo();
+ DexMethod invokedMethod = virtualBridgeInfo.getInvokedMethod();
+ assert !appView.appInfo().isStrictSubtypeOf(invokedMethod.holder, holder.type);
+ if (invokedMethod.holder == holder.type) {
+ return false;
+ }
+ assert appView.appInfo().isStrictSubtypeOf(holder.type, invokedMethod.holder);
+ return true;
+ }
+ assert false;
+ return false;
+ }
+
+ private void hoistBridgeIfPossible(
+ DexMethod method, DexProgramClass clazz, Set<DexProgramClass> subclasses) {
+ // If the method is defined on the parent class, we cannot hoist the bridge.
+ // TODO(b/153147967): If the declared method is abstract, we could replace it by the bridge.
+ // Add a test.
+ if (clazz.lookupMethod(method) != null) {
+ return;
+ }
+
+ // Go through each of the subclasses and bail-out if each subclass does not declare the same
+ // bridge.
+ BridgeInfo firstBridgeInfo = null;
+ for (DexProgramClass subclass : subclasses) {
+ DexEncodedMethod definition = subclass.lookupVirtualMethod(method);
+ if (definition == null) {
+ DexEncodedMethod resolutionTarget =
+ appView.appInfo().resolveMethodOnClass(subclass, method).getSingleTarget();
+ if (resolutionTarget == null || resolutionTarget.isAbstract()) {
+ // The fact that this class does not declare the bridge (or the bridge is abstract) should
+ // not prevent us from hoisting the bridge.
+ //
+ // Strictly speaking, there could be an invoke instruction that targets the bridge on this
+ // subclass and fails with an AbstractMethodError or a NoSuchMethodError in the input
+ // program. After hoisting the bridge to the superclass such an instruction would no
+ // longer fail with an error in the generated program.
+ //
+ // If this ever turns out be an issue, it would be possible to track if there is an invoke
+ // instruction targeting the bridge on this subclass that fails in the Enqueuer, but this
+ // should never be the case in practice.
+ continue;
+ }
+ return;
+ }
+
+ BridgeInfo currentBridgeInfo = definition.getOptimizationInfo().getBridgeInfo();
+ if (currentBridgeInfo == null) {
+ return;
+ }
+
+ if (firstBridgeInfo == null) {
+ firstBridgeInfo = currentBridgeInfo;
+ } else if (!currentBridgeInfo.hasSameTarget(firstBridgeInfo)) {
+ return;
+ }
+ }
+
+ // If we reached this point, it is because all of the subclasses define the same bridge.
+ assert firstBridgeInfo != null;
+
+ // Choose one of the bridge definitions as the one that we will be moving to the superclass.
+ ProgramMethod representative = findRepresentative(subclasses, method);
+ assert representative != null;
+
+ // Guard against accessibility issues.
+ if (mayBecomeInaccessibleAfterHoisting(clazz, representative)) {
+ return;
+ }
+
+ // Move the bridge method to the super class, and record this in the graph lens.
+ DexMethod newMethod =
+ appView.dexItemFactory().createMethod(clazz.type, method.proto, method.name);
+ clazz.addVirtualMethod(representative.getMethod().toTypeSubstitutedMethod(newMethod));
+ lensBuilder.move(representative.getMethod().method, newMethod);
+
+ // Remove all of the bridges in the subclasses.
+ for (DexProgramClass subclass : subclasses) {
+ DexEncodedMethod removed = subclass.removeMethod(method);
+ assert removed == null || !appView.appInfo().isPinned(removed.method);
+ }
+ }
+
+ private ProgramMethod findRepresentative(Iterable<DexProgramClass> subclasses, DexMethod method) {
+ for (DexProgramClass subclass : subclasses) {
+ DexEncodedMethod definition = subclass.lookupVirtualMethod(method);
+ if (definition != null) {
+ return new ProgramMethod(subclass, definition);
+ }
+ }
+ return null;
+ }
+
+ private boolean mayBecomeInaccessibleAfterHoisting(
+ DexProgramClass clazz, ProgramMethod representative) {
+ if (clazz.type.isSamePackage(representative.getHolder().type)) {
+ return false;
+ }
+ return !representative.getMethod().isPublic();
+ }
+
+ static class BridgeHoistingLens extends NestedGraphLense {
+
+ public BridgeHoistingLens(
+ AppView<?> appView, BiMap<DexMethod, DexMethod> originalMethodSignatures) {
+ super(
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ null,
+ originalMethodSignatures,
+ appView.graphLense(),
+ appView.dexItemFactory());
+ }
+
+ @Override
+ public boolean isLegitimateToHaveEmptyMappings() {
+ return true;
+ }
+
+ static class Builder {
+
+ private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+
+ public boolean isEmpty() {
+ return originalMethodSignatures.isEmpty();
+ }
+
+ public void move(DexMethod from, DexMethod to) {
+ originalMethodSignatures.forcePut(to, originalMethodSignatures.getOrDefault(from, from));
+ }
+
+ public BridgeHoistingLens build(AppView<?> appView) {
+ assert !isEmpty();
+ return new BridgeHoistingLens(appView, originalMethodSignatures);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index f847e9c..c252c04 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -46,6 +46,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.PredicateSet;
+import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.Visibility;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
@@ -745,6 +746,14 @@
return result;
}
+ public Set<DexType> getDeadProtoTypes() {
+ return deadProtoTypes;
+ }
+
+ public Set<DexType> getMissingTypes() {
+ return missingTypes;
+ }
+
public EnumValueInfoMapCollection getEnumValueInfoMapCollection() {
assert checkIfObsolete();
return enumValueInfoMaps;
@@ -928,7 +937,6 @@
return Collections.unmodifiableSortedMap(result);
}
- @Override
public boolean isInstantiatedInterface(DexProgramClass clazz) {
assert checkIfObsolete();
return objectAllocationInfoCollection.isInterfaceWithUnknownSubtypeHierarchy(clazz);
@@ -1377,4 +1385,47 @@
|| isInstantiatedInterface(clazz.asProgramClass());
}
}
+
+ public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeElement type) {
+ // Special case for java.lang.Object.
+ if (type.getClassType() == dexItemFactory().objectType) {
+ if (type.getInterfaces().isEmpty()) {
+ // The type java.lang.Object could be any instantiated type. Assume a finalizer exists.
+ return true;
+ }
+ for (DexType iface : type.getInterfaces()) {
+ if (mayHaveFinalizer(iface)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return mayHaveFinalizer(type.getClassType());
+ }
+
+ private boolean mayHaveFinalizer(DexType type) {
+ // A type may have an active finalizer if any derived instance has a finalizer.
+ return objectAllocationInfoCollection
+ .traverseInstantiatedSubtypes(
+ type,
+ clazz -> {
+ if (objectAllocationInfoCollection.isInterfaceWithUnknownSubtypeHierarchy(clazz)) {
+ return TraversalContinuation.BREAK;
+ } else {
+ SingleResolutionResult resolution =
+ resolveMethod(clazz, dexItemFactory().objectMembers.finalize)
+ .asSingleResolution();
+ if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
+ return TraversalContinuation.BREAK;
+ }
+ }
+ return TraversalContinuation.CONTINUE;
+ },
+ lambda -> {
+ // Lambda classes do not have finalizers.
+ return TraversalContinuation.CONTINUE;
+ },
+ this)
+ .shouldBreak();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
index 0f1b348..32a8501 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
@@ -33,14 +33,9 @@
public void modify(AppInfoWithLiveness appInfo) {
// Instantiated classes.
+ noLongerInstantiatedClasses.forEach(appInfo::removeFromSingleTargetLookupCache);
appInfo.mutateObjectAllocationInfoCollection(
- mutator -> {
- noLongerInstantiatedClasses.forEach(
- clazz -> {
- mutator.markNoLongerInstantiated(clazz);
- appInfo.removeFromSingleTargetLookupCache(clazz);
- });
- });
+ mutator -> noLongerInstantiatedClasses.forEach(mutator::markNoLongerInstantiated));
// Written fields.
FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
appInfo.getMutableFieldAccessInfoCollection();
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 4b599bf..1319195 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -228,6 +228,12 @@
/** Set of missing types. */
private final Set<DexType> missingTypes = Sets.newIdentityHashSet();
+ /** Set of proto types that were found to be dead during the first round of tree shaking. */
+ private Set<DexType> initialDeadProtoTypes;
+
+ /** Set of types that were found to be missing during the first round of tree shaking. */
+ private Set<DexType> initialMissingTypes;
+
/** Mapping from each unused interface to the set of live types that implements the interface. */
private final Map<DexProgramClass, Set<DexProgramClass>> unusedInterfaceTypes =
new IdentityHashMap<>();
@@ -416,6 +422,16 @@
this.annotationRemoverBuilder = annotationRemoverBuilder;
}
+ public void setInitialDeadProtoTypes(Set<DexType> initialDeadProtoTypes) {
+ assert mode.isFinalTreeShaking();
+ this.initialDeadProtoTypes = initialDeadProtoTypes;
+ }
+
+ public void setInitialMissingTypes(Set<DexType> initialMissingTypes) {
+ assert mode.isFinalTreeShaking();
+ this.initialMissingTypes = initialMissingTypes;
+ }
+
public void addDeadProtoTypeCandidate(DexType type) {
assert type.isProgramType(appView);
addDeadProtoTypeCandidate(appView.definitionFor(type).asProgramClass());
@@ -726,6 +742,12 @@
return isRead ? info.recordRead(field, context) : info.recordWrite(field, context);
}
+ private boolean isStringConcat(DexMethodHandle bootstrapMethod) {
+ return bootstrapMethod.type.isInvokeStatic()
+ && (bootstrapMethod.asMethod() == appView.dexItemFactory().stringConcatWithConstantsMethod
+ || bootstrapMethod.asMethod() == appView.dexItemFactory().stringConcatMethod);
+ }
+
void traceCallSite(DexCallSite callSite, ProgramMethod context) {
DexProgramClass bootstrapClass =
getProgramClassOrNull(callSite.bootstrapMethod.asMethod().holder);
@@ -736,7 +758,7 @@
DexProgramClass contextHolder = context.getHolder();
LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, contextHolder);
if (descriptor == null) {
- if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
+ if (!isStringConcat(callSite.bootstrapMethod)) {
if (options.reporter != null) {
Diagnostic message =
new StringDiagnostic(
@@ -955,10 +977,7 @@
return appView.withGeneratedMessageLiteBuilderShrinker(
shrinker ->
shrinker.deferDeadProtoBuilders(
- clazz,
- currentMethod,
- () -> liveTypes.registerDeferredAction(clazz, action),
- this),
+ clazz, currentMethod, () -> liveTypes.registerDeferredAction(clazz, action)),
false);
}
return false;
@@ -1803,6 +1822,11 @@
}
private void reportMissingClass(DexType clazz) {
+ assert !mode.isFinalTreeShaking()
+ || appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz)
+ || (initialDeadProtoTypes != null && initialDeadProtoTypes.contains(clazz))
+ || initialMissingTypes.contains(clazz)
+ : "Unexpected missing class `" + clazz.toSourceString() + "`";
boolean newReport = missingTypes.add(clazz);
if (Log.ENABLED && newReport) {
Log.verbose(Enqueuer.class, "Class `%s` is missing.", clazz);
@@ -3512,7 +3536,10 @@
return;
}
if (identifierItem.isDexType()) {
- DexProgramClass clazz = getProgramClassOrNull(identifierItem.asDexType());
+ // This is using appView.definitionFor() to avoid that we report reflectively accessed types
+ // as missing.
+ DexProgramClass clazz =
+ asProgramClassOrNull(appView.definitionFor(identifierItem.asDexType()));
if (clazz == null) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index e716083..a949527 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -7,7 +7,9 @@
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.Enqueuer.Mode;
+import java.util.Set;
public class EnqueuerFactory {
@@ -17,8 +19,14 @@
}
public static Enqueuer createForFinalTreeShaking(
- AppView<? extends AppInfoWithSubtyping> appView, GraphConsumer keptGraphConsumer) {
- return new Enqueuer(appView, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
+ AppView<? extends AppInfoWithSubtyping> appView,
+ GraphConsumer keptGraphConsumer,
+ Set<DexType> initialMissingTypes) {
+ Enqueuer enqueuer = new Enqueuer(appView, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
+ appView.withProtoShrinker(
+ shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
+ enqueuer.setInitialMissingTypes(initialMissingTypes);
+ return enqueuer;
}
public static Enqueuer createForMainDexTracing(AppView<? extends AppInfoWithSubtyping> appView) {
diff --git a/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
index 1691758..a2f246e 100644
--- a/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
+++ b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
@@ -4,11 +4,13 @@
package com.android.tools.r8.shaking;
-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.utils.TraversalContinuation;
+import com.google.common.collect.Sets;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class SingleTargetLookupCache {
@@ -25,14 +27,25 @@
}
public void removeInstantiatedType(DexType instantiatedType, AppInfoWithLiveness appInfo) {
- // Remove all types in the hierarchy related to this type.
+ // Remove all types in the instantiated hierarchy related to this type.
cache.remove(instantiatedType);
- DexClass clazz = appInfo.definitionFor(instantiatedType);
- if (clazz == null) {
- return;
- }
- appInfo.forEachSuperType(clazz, (type, ignore) -> cache.remove(type));
- appInfo.subtypes(instantiatedType).forEach(cache::remove);
+ Set<DexType> seen = Sets.newIdentityHashSet();
+ appInfo.forEachInstantiatedSubType(
+ instantiatedType,
+ instance ->
+ appInfo.traverseSuperTypes(
+ instance,
+ (type, ignore) -> {
+ if (seen.add(type)) {
+ cache.remove(type);
+ return TraversalContinuation.CONTINUE;
+ } else {
+ return TraversalContinuation.BREAK;
+ }
+ }),
+ lambda -> {
+ assert false;
+ });
}
public DexEncodedMethod getCachedItem(DexType receiverType, DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 40324ec..4ce026a 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.Map;
import java.util.function.Function;
+import java.util.function.IntPredicate;
import java.util.function.Predicate;
public class ArrayUtils {
@@ -106,6 +107,14 @@
clazz.cast(Array.newInstance(clazz.getComponentType(), results.size())));
}
+ public static int[] createIdentityArray(int size) {
+ int[] array = new int[size];
+ for (int i = 0; i < size; i++) {
+ array[i] = i;
+ }
+ return array;
+ }
+
public static <T> boolean contains(T[] elements, T elementToLookFor) {
for (Object element : elements) {
if (element.equals(elementToLookFor)) {
@@ -114,4 +123,18 @@
}
return false;
}
+
+ public static int[] fromPredicate(IntPredicate predicate, int size) {
+ int[] result = new int[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = BooleanUtils.intValue(predicate.test(i));
+ }
+ return result;
+ }
+
+ public static void sumOfPredecessorsInclusive(int[] array) {
+ for (int i = 1; i < array.length; i++) {
+ array[i] += array[i - 1];
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index de25a3b..196091e 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -355,7 +355,7 @@
*/
public static String getDescriptorFromClassBinaryName(String typeBinaryName) {
assert typeBinaryName != null;
- return ('L' + typeBinaryName + ';');
+ return 'L' + typeBinaryName + ';';
}
/**
diff --git a/src/main/java/com/android/tools/r8/utils/ForEachable.java b/src/main/java/com/android/tools/r8/utils/ForEachable.java
new file mode 100644
index 0000000..4c8ae53
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ForEachable.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import java.util.function.Consumer;
+
+public interface ForEachable<T> {
+
+ void forEach(Consumer<T> consumer);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 6ab3fcd..b58e9d6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.errors.InvalidDebugInfoException;
import com.android.tools.r8.errors.InvalidLibrarySuperclassDiagnostic;
import com.android.tools.r8.errors.MissingNestHostNestDesugarDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.graph.AppView;
@@ -162,7 +163,6 @@
void enableProtoShrinking() {
applyInliningToInlinee = true;
enableFieldBitAccessAnalysis = true;
- enableStringSwitchConversion = true;
protoShrinking.enableGeneratedMessageLiteShrinking = true;
protoShrinking.enableGeneratedMessageLiteBuilderShrinking = true;
protoShrinking.enableGeneratedExtensionRegistryShrinking = true;
@@ -273,8 +273,8 @@
// the actual catch handler allowed when inlining. Threshold found empirically by testing on
// GMS Core.
public int inliningControlFlowResolutionBlocksThreshold = 15;
- public boolean enableStringSwitchConversion =
- System.getProperty("com.android.tools.r8.stringSwitchConversion") != null;
+ public boolean enableStringSwitchConversion = true;
+ public int minimumStringSwitchSize = 3;
public boolean enableEnumValueOptimization = true;
public boolean enableEnumSwitchMapRemoval = true;
public final OutlineOptions outline = new OutlineOptions();
@@ -701,6 +701,12 @@
return assertionsEnabled;
}
+ public static void checkAssertionsEnabled() {
+ if (!assertionsEnabled()) {
+ throw new Unreachable();
+ }
+ }
+
/** A set of dexitems we have reported missing to dedupe warnings. */
private final Set<DexItem> reportedMissingForDesugaring = Sets.newConcurrentHashSet();
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index fdf7e98..02e83de 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -11,6 +11,8 @@
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class StringUtils {
public static char[] EMPTY_CHAR_ARRAY = {};
@@ -327,4 +329,8 @@
}
return string.length();
}
+
+ public static String replaceAll(String subject, String target, String replacement) {
+ return subject.replaceAll(Pattern.quote(target), Matcher.quoteReplacement(replacement));
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 84aa0d0..e6076ca 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -21,32 +21,37 @@
public static <T, R, E extends Exception> Collection<R> processItemsWithResults(
Iterable<T> items, ThrowingFunction<T, R, E> consumer, ExecutorService executorService)
throws ExecutionException {
+ return processItemsWithResults(items::forEach, consumer, executorService);
+ }
+
+ public static <T, R, E extends Exception> Collection<R> processItemsWithResults(
+ ForEachable<T> items, ThrowingFunction<T, R, E> consumer, ExecutorService executorService)
+ throws ExecutionException {
List<Future<R>> futures = new ArrayList<>();
- for (T item : items) {
- futures.add(executorService.submit(() -> consumer.apply(item)));
- }
+ items.forEach(item -> futures.add(executorService.submit(() -> consumer.apply(item))));
return awaitFuturesWithResults(futures);
}
public static <T, E extends Exception> void processItems(
Iterable<T> items, ThrowingConsumer<T, E> consumer, ExecutorService executorService)
throws ExecutionException {
- processItemsWithResults(
- items,
- arg -> {
- consumer.accept(arg);
- return null;
- },
- executorService);
+ processItems(items::forEach, consumer, executorService);
}
public static <T, U, E extends Exception> void processItems(
Map<T, U> items, ThrowingBiConsumer<T, U, E> consumer, ExecutorService executorService)
throws ExecutionException {
+ processItems(
+ items.entrySet(), arg -> consumer.accept(arg.getKey(), arg.getValue()), executorService);
+ }
+
+ public static <T, E extends Exception> void processItems(
+ ForEachable<T> items, ThrowingConsumer<T, E> consumer, ExecutorService executorService)
+ throws ExecutionException {
processItemsWithResults(
- items.entrySet(),
+ items,
arg -> {
- consumer.accept(arg.getKey(), arg.getValue());
+ consumer.accept(arg);
return null;
},
executorService);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index ac15d64..743a898 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -37,6 +37,7 @@
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
@@ -235,8 +236,8 @@
return ClassFileTransformer.create(clazz);
}
- public static ClassFileTransformer transformer(byte[] clazz) {
- return ClassFileTransformer.create(clazz);
+ public static ClassFileTransformer transformer(byte[] bytes, ClassReference classReference) {
+ return ClassFileTransformer.create(bytes, classReference);
}
// Actually running Proguard should only be during development.
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e184d97..080051f 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1061,6 +1061,11 @@
return paths;
}
+ public static Collection<Path> getClassFilesForInnerClasses(Class<?>... classes)
+ throws IOException {
+ return getClassFilesForInnerClasses(Arrays.asList(classes));
+ }
+
public static Path getFileNameForTestClass(Class clazz) {
List<String> parts = getNamePartsForTestClass(clazz);
return Paths.get("", parts.toArray(StringUtils.EMPTY_ARRAY));
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 9fe3bb4..2c46b40 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.bridgeremoval;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -141,18 +142,24 @@
// Cls1#foo and Cls2#foo should not refer to each other.
// They can invoke their own bridge method or AbsCls#foo (via member rebinding).
- MethodSubject fooInCls2 =
- cls2Subject.method("void", "foo", ImmutableList.of("java.lang.Integer"));
- assertThat(fooInCls2, isPresent());
- DexCode code = fooInCls2.getMethod().getCode().asDexCode();
+ // Cls2#foo has been moved to AbsCls#foo as a result of bridge hoisting.
+ MethodSubject fooInCls2 = cls2Subject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooInCls2, not(isPresent()));
+
+ MethodSubject fooFromCls2InAbsCls = absSubject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooFromCls2InAbsCls, isPresent());
+ DexCode code = fooFromCls2InAbsCls.getMethod().getCode().asDexCode();
checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
assertEquals(absSubject.getDexClass().type, invoke.getMethod().holder);
- MethodSubject fooInCls1 =
- cls1Subject.method("void", "foo", ImmutableList.of("java.lang.String"));
- assertThat(fooInCls1, isPresent());
- code = fooInCls1.getMethod().getCode().asDexCode();
+ // Cls1#foo has been moved to AbsCls#foo as a result of bridge hoisting.
+ MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.String");
+ assertThat(fooInCls1, not(isPresent()));
+
+ MethodSubject fooFromCls1InAbsCls = absSubject.method("void", "foo", "java.lang.String");
+ assertThat(fooFromCls1InAbsCls, isPresent());
+ code = fooFromCls1InAbsCls.getMethod().getCode().asDexCode();
checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
invoke = (InvokeVirtual) code.instructions[0];
assertEquals(absSubject.getDexClass().type, invoke.getMethod().holder);
@@ -247,18 +254,20 @@
// Cls1#foo and Cls2#bar should refer to Base#foo.
- MethodSubject barInCls2 =
- cls2Subject.method("void", "bar", ImmutableList.of("java.lang.String"));
+ MethodSubject barInCls2 = cls2Subject.method("void", "bar", "java.lang.String");
assertThat(barInCls2, isPresent());
DexCode code = barInCls2.getMethod().getCode().asDexCode();
checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder);
- MethodSubject fooInCls1 =
- cls1Subject.method("void", "foo", ImmutableList.of("java.lang.Integer"));
- assertThat(fooInCls1, isPresent());
- code = fooInCls1.getMethod().getCode().asDexCode();
+ // Cls1#foo has been moved to Base#foo as a result of bridge hoisting.
+ MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooInCls1, not(isPresent()));
+
+ MethodSubject fooInBase = baseSubject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooInBase, isPresent());
+ code = fooInBase.getMethod().getCode().asDexCode();
checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
invoke = (InvokeVirtual) code.instructions[0];
assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder);
@@ -341,8 +350,7 @@
// DerivedString2#bar should refer to Base#foo.
- MethodSubject barInSub =
- subSubject.method("void", "bar", ImmutableList.of("java.lang.String"));
+ MethodSubject barInSub = subSubject.method("void", "bar", "java.lang.String");
assertThat(barInSub, isPresent());
DexCode code = barInSub.getMethod().getCode().asDexCode();
checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
@@ -415,8 +423,7 @@
// Base#bar should remain as-is, i.e., refer to Base#foo(Object).
- MethodSubject barInSub =
- baseSubject.method("void", "bar", ImmutableList.of("java.lang.String"));
+ MethodSubject barInSub = baseSubject.method("void", "bar", "java.lang.String");
assertThat(barInSub, isPresent());
DexCode code = barInSub.getMethod().getCode().asDexCode();
checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
index ebb7662..16db7d8 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -61,6 +61,7 @@
ClassSubject aClassSubject = inspector.clazz(BridgeHoistingAccessibilityTestClasses.A.class);
assertThat(aClassSubject, isPresent());
assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
+ assertThat(aClassSubject.uniqueMethodWithName("bridgeC"), isPresent());
ClassSubject bClassSubject = inspector.clazz(B.class);
assertThat(bClassSubject, isPresent());
@@ -68,8 +69,6 @@
ClassSubject cClassSubject = inspector.clazz(C.class);
assertThat(cClassSubject, isPresent());
- // TODO(b/153147967): Should be hoisted to A.
- assertThat(cClassSubject.uniqueMethodWithName("bridgeC"), isPresent());
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
index 362f6a2..d6eb6cd 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
@@ -60,11 +60,11 @@
ClassSubject bClassSubject = inspector.clazz(NonReboundBridgeHoistingTestClasses.B.class);
assertThat(bClassSubject, isPresent());
+ assertThat(bClassSubject.uniqueMethodWithName("bridge"), isPresent());
ClassSubject cClassSubject = inspector.clazz(C.class);
assertThat(cClassSubject, isPresent());
- // TODO(b/153147967): This bridge should be hoisted to B.
- assertThat(cClassSubject.uniqueMethodWithName("bridge"), isPresent());
+ assertThat(cClassSubject.uniqueMethodWithName("bridge"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
index 50fc23c..89437f1 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
@@ -59,24 +59,18 @@
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
- // TODO(b/153147967): This should become present after hoisting.
- assertThat(aClassSubject.uniqueMethodWithName("superBridge"), not(isPresent()));
- // TODO(b/153147967): This should become present after hoisting.
- assertThat(aClassSubject.uniqueMethodWithName("virtualBridge"), not(isPresent()));
+ assertThat(aClassSubject.uniqueMethodWithName("superBridge"), isPresent());
+ assertThat(aClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
ClassSubject b1ClassSubject = inspector.clazz(B1.class);
assertThat(b1ClassSubject, isPresent());
- // TODO(b/153147967): This bridge should be hoisted to A.
- assertThat(b1ClassSubject.uniqueMethodWithName("superBridge"), isPresent());
- // TODO(b/153147967): This bridge should be hoisted to A.
- assertThat(b1ClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
+ assertThat(b1ClassSubject.uniqueMethodWithName("superBridge"), not(isPresent()));
+ assertThat(b1ClassSubject.uniqueMethodWithName("virtualBridge"), not(isPresent()));
ClassSubject b2ClassSubject = inspector.clazz(B2.class);
assertThat(b2ClassSubject, isPresent());
- // TODO(b/153147967): This bridge should be hoisted to A.
- assertThat(b2ClassSubject.uniqueMethodWithName("superBridge"), isPresent());
- // TODO(b/153147967): This bridge should be hoisted to A.
- assertThat(b2ClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
+ assertThat(b2ClassSubject.uniqueMethodWithName("superBridge"), not(isPresent()));
+ assertThat(b2ClassSubject.uniqueMethodWithName("virtualBridge"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 8096c47..e6fdf99 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -35,6 +35,9 @@
System.out.println("Skipping check for " + apiLevel);
continue;
}
+ if (apiLevel == AndroidApiLevel.R) {
+ continue;
+ }
// Check that the backported methods for each API level are are not present in the
// android.jar for that level.
CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
new file mode 100644
index 0000000..eefe0f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -0,0 +1,145 @@
+// Copyright (c) 2020, 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.desugar.desugaredlibrary;
+
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MergingWithDesugaredLibraryTest extends Jdk11DesugaredLibraryTestBase {
+
+ private static final String JAVA_RESULT = "java.util.stream.ReferencePipeline$Head";
+ private static final String J$_RESULT = "j$.util.stream.ReferencePipeline$Head";
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public MergingWithDesugaredLibraryTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testMergeDesugaredAndNonDesugared() throws Exception {
+ D8TestCompileResult compileResult =
+ testForD8()
+ .addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary, parameters.getApiLevel());
+ // TODO(b/154106502): This should raise a proper warning. The dex files are incompatible,
+ // so the behavior is undefined regarding desugared types.
+ if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
+ compileResult
+ .run(parameters.getRuntime(), Part1.class)
+ .assertSuccessWithOutputLines(J$_RESULT);
+ } else {
+ compileResult
+ .run(parameters.getRuntime(), Part1.class)
+ .assertSuccessWithOutputLines(JAVA_RESULT);
+ }
+ if (parameters.getRuntime().asDex().getMinApiLevel().getLevel()
+ < AndroidApiLevel.N.getLevel()) {
+ compileResult
+ .run(parameters.getRuntime(), Part2.class)
+ .assertFailureWithErrorThatMatches(containsString("java.lang.NoSuchMethodError"));
+ } else {
+
+ compileResult
+ .run(parameters.getRuntime(), Part2.class)
+ .assertSuccessWithOutputLines(JAVA_RESULT);
+ }
+ }
+
+ @Test
+ public void testMergeDesugaredAndClassFile() throws Exception {
+ D8TestCompileResult compileResult =
+ testForD8()
+ .addProgramFiles(buildPart1DesugaredLibrary())
+ .addProgramClasses(Part2.class)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(this::assertWarningPresent)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary, parameters.getApiLevel());
+ if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
+ compileResult
+ .run(parameters.getRuntime(), Part1.class)
+ .assertSuccessWithOutputLines(J$_RESULT);
+ compileResult
+ .run(parameters.getRuntime(), Part2.class)
+ .assertSuccessWithOutputLines(J$_RESULT);
+ } else {
+ compileResult
+ .run(parameters.getRuntime(), Part1.class)
+ .assertSuccessWithOutputLines(JAVA_RESULT);
+ compileResult
+ .run(parameters.getRuntime(), Part2.class)
+ .assertSuccessWithOutputLines(JAVA_RESULT);
+ }
+ }
+
+ private void assertWarningPresent(TestDiagnosticMessages testDiagnosticMessages) {
+ if (parameters.getApiLevel().getLevel() > AndroidApiLevel.N.getLevel()) {
+ return;
+ }
+ assertTrue(
+ testDiagnosticMessages.getWarnings().stream()
+ .anyMatch(
+ warn -> warn.getDiagnosticMessage().startsWith("The compilation is slowed down")));
+ }
+
+ private Path buildPart1DesugaredLibrary() throws Exception {
+ return testForD8()
+ .addProgramClasses(Part1.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ }
+
+ private Path buildPart2NoDesugaredLibrary() throws Exception {
+ return testForD8()
+ .addProgramClasses(Part2.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ }
+
+ @SuppressWarnings("RedundantOperationOnEmptyContainer")
+ static class Part1 {
+ public static void main(String[] args) {
+ System.out.println(new ArrayList<>().stream().getClass().getName());
+ }
+ }
+
+ @SuppressWarnings("RedundantOperationOnEmptyContainer")
+ static class Part2 {
+ public static void main(String[] args) {
+ System.out.println(new ArrayList<>().stream().getClass().getName());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
new file mode 100644
index 0000000..ef584d2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, 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.desugar.desugaredlibrary.conversiontests;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.ZoneId;
+import java.util.TimeZone;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConversionAndMergeTest extends DesugaredLibraryTestBase {
+
+ private static final String GMT = StringUtils.lines("GMT");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getConversionParametersUpToExcluding(AndroidApiLevel.O);
+ }
+
+ public ConversionAndMergeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testMerge() throws Exception {
+ Path extra = buildClass(ExtraClass.class);
+ Path convClass = buildClass(APIConversionClass.class);
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramFiles(extra, convClass)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+ .run(parameters.getRuntime(), APIConversionClass.class)
+ .assertSuccessWithOutput(GMT);
+ }
+
+ private Path buildClass(Class<?> cls) throws Exception {
+ return testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(cls)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ }
+
+ static class ExtraClass {
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+ }
+
+ static class APIConversionClass {
+ public static void main(String[] args) {
+ // Following is a call where java.time.ZoneId is a parameter type (getTimeZone()).
+ TimeZone timeZone = TimeZone.getTimeZone(ZoneId.systemDefault());
+ // Following is a call where java.time.ZoneId is a return type (toZoneId()).
+ System.out.println(timeZone.toZoneId().getId());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java
new file mode 100644
index 0000000..c65427a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NullOutValueInvokeEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final KeepRule enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public NullOutValueInvokeEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = NullOutValueInvoke.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classToTest, ENUM_CLASS)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class NullOutValueInvoke {
+
+ public static void main(String[] args) {
+ printAndGetMyEnum();
+ printAndGetMyEnum();
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ @NeverInline
+ static MyEnum printAndGetMyEnum() {
+ System.out.println("print");
+ return MyEnum.B;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
index 355c6a1..fff500b 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.enumunboxing;
import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import java.util.List;
import org.junit.Test;
@@ -15,7 +16,7 @@
@RunWith(Parameterized.class)
public class PinnedEnumUnboxingTest extends EnumUnboxingTestBase {
- private static final Class<?> ENUM_CLASS = MyEnum.class;
+ private static final Class<?>[] BOXED = {MainWithKeptEnum.class, MainWithKeptEnumArray.class};
private final TestParameters parameters;
private final boolean enumValueOptimization;
@@ -35,34 +36,56 @@
@Test
public void testEnumUnboxing() throws Exception {
- Class<MainWithKeptEnum> classToTest = MainWithKeptEnum.class;
- testForR8(parameters.getBackend())
- .addProgramClasses(classToTest, ENUM_CLASS)
- .addKeepMainRule(classToTest)
- .addKeepClassRules(MyEnum.class)
- .addKeepRules(enumKeepRules.getKeepRule())
- .enableNeverClassInliningAnnotations()
- .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
- .allowDiagnosticInfoMessages()
- .setMinApi(parameters.getApiLevel())
- .compile()
- .inspectDiagnosticMessages(
- m -> assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m))
- .run(parameters.getRuntime(), classToTest)
- .assertSuccessWithOutputLines("0");
- }
-
- @NeverClassInline
- enum MyEnum {
- A,
- B,
- C
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(PinnedEnumUnboxingTest.class)
+ .addKeepMainRules(BOXED)
+ .addKeepClassRules(MainWithKeptEnum.MyEnum.class)
+ .addKeepMethodRules(MainWithKeptEnumArray.class, "keptMethod()")
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .enableNeverClassInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ for (Class<?> boxed : BOXED) {
+ compileResult
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsBoxed(boxed.getDeclaredClasses()[0], boxed.getSimpleName(), m))
+ .run(parameters.getRuntime(), boxed)
+ .assertSuccessWithOutputLines("0");
+ }
}
static class MainWithKeptEnum {
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
public static void main(String[] args) {
System.out.println(MyEnum.A.ordinal());
}
}
+
+ static class MainWithKeptEnumArray {
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ System.out.println(MyEnum.A.ordinal());
+ }
+
+ public static MyEnum[] keptMethod() {
+ System.out.println("KEPT");
+ return null;
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
index 9644e0d..2a700c3 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
@@ -24,7 +24,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import java.util.List;
-import java.util.Objects;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,7 +37,8 @@
@Parameterized.Parameters(name = "{1}, enable string switch conversion: {0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
public StringSwitchConversionFromIfTest(
@@ -53,15 +53,12 @@
.addInnerClasses(StringSwitchConversionFromIfTest.class)
.addKeepMainRule(TestClass.class)
.addOptionsModification(
- options -> {
- assert !options.enableStringSwitchConversion;
- options.enableStringSwitchConversion = enableStringSwitchConversion;
- })
+ options -> options.enableStringSwitchConversion = enableStringSwitchConversion)
.enableInliningAnnotations()
// TODO(b/135560746): Add support for treating the keys of a string-switch instruction as an
// identifier name string.
.noMinification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
inspector -> {
@@ -96,15 +93,6 @@
// compiled to a sequence of `if (x.equals("..."))` instructions that do not even
// use the hash code.
assertNotEquals(0, hashCodeValues.size());
-
- // We indirectly verify that the string-switch instructions have been identified
- // by checking that none of the hash codes are used for anything. Note that this
- // only holds with a backend that compiles string-switch instruction to
- // `if (x.equals("..."))` instructions that do not use the hash code for anything.
- assertEquals(
- methodName,
- enableStringSwitchConversion,
- hashCodeValues.stream().filter(Objects::nonNull).noneMatch(Value::isUsed));
}
})
.run(parameters.getRuntime(), TestClass.class)
@@ -139,25 +127,28 @@
private static final int HASH_2 = -1340982898;
public static void main(String[] args) {
- test(A.class);
- test(B.class);
- testWithDeadStringIdComparisons(A.class);
- testWithDeadStringIdComparisons(B.class);
- testWithMultipleHashCodeInvocations(A.class);
- testWithMultipleHashCodeInvocations(B.class);
- testWithSwitch(A.class);
- testWithSwitch(B.class);
+ test(toNullableClassName(A.class));
+ test(toNullableClassName(B.class));
+ testWithDeadStringIdComparisons(toNullableClassName(A.class));
+ testWithDeadStringIdComparisons(toNullableClassName(B.class));
+ testWithMultipleHashCodeInvocations(toNullableClassName(A.class));
+ testWithMultipleHashCodeInvocations(toNullableClassName(B.class));
+ testWithSwitch(toNullableClassName(A.class));
+ testWithSwitch(toNullableClassName(B.class));
try {
- testNullCheckIsPreserved(System.currentTimeMillis() >= 0 ? null : A.class);
+ testNullCheckIsPreserved(System.currentTimeMillis() >= 0 ? null : A.class.getName());
} catch (NullPointerException e) {
System.out.println("Caught NPE");
}
}
+ static String toNullableClassName(Class<?> clazz) {
+ return System.currentTimeMillis() > 0 ? clazz.getName() : null;
+ }
+
@NeverInline
- private static void test(Class<?> clazz) {
- String className = clazz.getName();
+ private static void test(String className) {
int hashCode = className.hashCode();
int result = -1;
if (hashCode == HASH_1) {
@@ -177,8 +168,7 @@
}
@NeverInline
- private static void testWithDeadStringIdComparisons(Class<?> clazz) {
- String className = clazz.getName();
+ private static void testWithDeadStringIdComparisons(String className) {
int hashCode = className.hashCode();
int result = -1;
if (hashCode == HASH_1) {
@@ -222,8 +212,7 @@
}
@NeverInline
- private static void testWithMultipleHashCodeInvocations(Class<?> clazz) {
- String className = clazz.getName();
+ private static void testWithMultipleHashCodeInvocations(String className) {
int result = -1;
if (className.hashCode() == HASH_1) {
if (className.equals(NAME_1)) {
@@ -242,8 +231,7 @@
}
@NeverInline
- private static void testWithSwitch(Class<?> clazz) {
- String className = clazz.getName();
+ private static void testWithSwitch(String className) {
int result = -1;
if (className.hashCode() == HASH_1) {
if (className.equals(NAME_1)) {
@@ -267,8 +255,8 @@
}
@NeverInline
- private static void testNullCheckIsPreserved(Class<?> clazz) {
- switch (clazz.getName()) {
+ private static void testNullCheckIsPreserved(String className) {
+ switch (className) {
case "A":
System.out.println("Unexpected (A)");
break;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/ConvertRemovedStringSwitchTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/ConvertRemovedStringSwitchTest.java
new file mode 100644
index 0000000..3243171
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/ConvertRemovedStringSwitchTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, 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.switches;
+
+import static com.android.tools.r8.utils.BooleanUtils.intValue;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConvertRemovedStringSwitchTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ConvertRemovedStringSwitchTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ConvertRemovedStringSwitchTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class, "A", "B", "C", "D", "E")
+ .assertSuccessWithOutputLines("A", "B", "C", "D", "E!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+
+ MethodSubject mainMethodSubject = classSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+
+ // Verify that the keys were canonicalized.
+ Object2IntMap<String> stringCounts = countStrings(mainMethodSubject);
+ assertEquals(1 + intValue(parameters.isCfRuntime()), stringCounts.getInt("A"));
+ assertEquals(1 + intValue(parameters.isCfRuntime()), stringCounts.getInt("B"));
+ assertEquals(1 + intValue(parameters.isCfRuntime()), stringCounts.getInt("C"));
+ assertEquals(1 + intValue(parameters.isCfRuntime()), stringCounts.getInt("D"));
+ assertEquals(1, stringCounts.getInt("E"));
+ assertEquals(1, stringCounts.getInt("E!"));
+
+ // Verify that we can rebuild the StringSwitch instruction.
+ IRCode code = mainMethodSubject.buildIR();
+ assertTrue(code.streamInstructions().anyMatch(Instruction::isStringSwitch));
+ }
+
+ private static Object2IntMap<String> countStrings(MethodSubject methodSubject) {
+ Object2IntMap<String> result = new Object2IntOpenHashMap<>();
+ methodSubject
+ .streamInstructions()
+ .filter(instruction -> instruction.isConstString(JumboStringMode.ALLOW))
+ .map(InstructionSubject::getConstString)
+ .forEach(string -> result.put(string, result.getInt(string) + 1));
+ return result;
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ for (String arg : args) {
+ switch (arg) {
+ case "A":
+ System.out.println("A");
+ break;
+ case "B":
+ System.out.println("B");
+ break;
+ case "C":
+ System.out.println("C");
+ break;
+ case "D":
+ System.out.println("D");
+ break;
+ case "E":
+ // Intentionally "E!" to prevent canonicalization of this key.
+ System.out.println("E!");
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchCaseRemovalWithCompileTimeHashCodeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchCaseRemovalWithCompileTimeHashCodeTest.java
index ff41e06..7660e79 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchCaseRemovalWithCompileTimeHashCodeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchCaseRemovalWithCompileTimeHashCodeTest.java
@@ -6,13 +6,15 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -20,14 +22,19 @@
@RunWith(Parameterized.class)
public class StringSwitchCaseRemovalWithCompileTimeHashCodeTest extends TestBase {
+
+ private final boolean enableStringSwitchConversion;
private final TestParameters parameters;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ @Parameters(name = "{1}, enable string-switch conversion: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public StringSwitchCaseRemovalWithCompileTimeHashCodeTest(TestParameters parameters) {
+ public StringSwitchCaseRemovalWithCompileTimeHashCodeTest(
+ boolean enableStringSwitchConversion, TestParameters parameters) {
+ this.enableStringSwitchConversion = enableStringSwitchConversion;
this.parameters = parameters;
}
@@ -38,30 +45,35 @@
.addKeepMainRule(TestClass.class)
.addOptionsModification(
options -> {
- // TODO(b/135721688): Once a backend is in place for StringSwitch instructions,
- // generalize switch case removal for IntSwitch instructions to Switch instructions.
- assert !options.enableStringSwitchConversion;
+ options.enableStringSwitchConversion = enableStringSwitchConversion;
+ assertTrue(options.minimumStringSwitchSize >= 3);
+ options.minimumStringSwitchSize = 2;
})
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("FOO")
- .inspect(codeInspector -> {
- ClassSubject main = codeInspector.clazz(TestClass.class);
- assertThat(main, isPresent());
- MethodSubject mainMethod = main.mainMethod();
- assertThat(mainMethod, isPresent());
- assertEquals(0, countCall(mainMethod, "String", "hashCode"));
- // Only "FOO" left
- assertEquals(
- 1,
- mainMethod.streamInstructions()
- .filter(i -> i.isConstString(JumboStringMode.ALLOW)).count());
- // No branching points.
- assertEquals(
- 0,
- mainMethod.streamInstructions()
- .filter(i -> i.isIf() || i.isIfEqz() || i.isIfNez()).count());
- });
+ .inspect(
+ codeInspector -> {
+ ClassSubject main = codeInspector.clazz(TestClass.class);
+ assertThat(main, isPresent());
+ MethodSubject mainMethod = main.mainMethod();
+ assertThat(mainMethod, isPresent());
+ assertEquals(0, countCall(mainMethod, "String", "hashCode"));
+ // Only "FOO" left
+ assertEquals(
+ 1,
+ mainMethod
+ .streamInstructions()
+ .filter(i -> i.isConstString(JumboStringMode.ALLOW))
+ .count());
+ // No branching points.
+ assertEquals(
+ 0,
+ mainMethod
+ .streamInstructions()
+ .filter(i -> i.isIf() || i.isIfEqz() || i.isIfNez())
+ .count());
+ });
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithHashCollisionsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithHashCollisionsTest.java
new file mode 100644
index 0000000..d4b6f89
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithHashCollisionsTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2020, 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.switches;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StringSwitchWithHashCollisionsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public StringSwitchWithHashCollisionsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assertEquals("Hello world!".hashCode(), "Hello worla~".hashCode());
+ assertEquals("Hello world!".hashCode(), "Hello worlb_".hashCode());
+ assertNotEquals("Hello world!".hashCode(), "_".hashCode());
+
+ testForR8(parameters.getBackend())
+ .addInnerClasses(StringSwitchWithHashCollisionsTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(options -> assertTrue(options.minimumStringSwitchSize >= 3))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(
+ parameters.getRuntime(),
+ TestClass.class,
+ "Hello world!",
+ "_",
+ "Hello worla~",
+ "_",
+ "Hello worlb_",
+ "_",
+ "DONE")
+ .assertSuccessWithOutputLines(
+ "Hello world!", "Hello world, ish!", "Hello world, ish2!", "DONE");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ for (String string : args) {
+ switch (string) {
+ case "Hello world!":
+ System.out.print("Hello world!");
+ break;
+
+ case "Hello worla~":
+ System.out.print("Hello world, ish!");
+ break;
+
+ case "Hello worlb_":
+ System.out.print("Hello world, ish2!");
+ break;
+
+ case "_":
+ System.out.println();
+ break;
+
+ default:
+ System.out.println("DONE");
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithSameTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithSameTargetTest.java
index 1de3022..f035481 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithSameTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithSameTargetTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.switches;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -19,7 +21,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public StringSwitchWithSameTargetTest(TestParameters parameters) {
@@ -33,10 +35,10 @@
.addKeepMainRule(TestClass.class)
.addOptionsModification(
options -> {
- assert !options.enableStringSwitchConversion; // Remove once default.
- options.enableStringSwitchConversion = true;
+ assertTrue(options.minimumStringSwitchSize >= 3);
+ options.minimumStringSwitchSize = 2;
})
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class, "Hello", "_", "world!")
.assertSuccessWithOutput("Hello world!");
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithMissingFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithMissingFieldTest.java
new file mode 100644
index 0000000..796b3d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithMissingFieldTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, 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.switches;
+
+import static com.android.tools.r8.ToolHelper.getClassFilesForInnerClasses;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This tests that we gracefully handle the case where a switch map is incomplete, i.e., the
+ * corresponding enum has fields that are not present in the enum switch map.
+ */
+@RunWith(Parameterized.class)
+public class SwitchMapWithMissingFieldTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SwitchMapWithMissingFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addProgramFiles(getSwitchMapProgramFile())
+ .addProgramClassFileData(
+ transformer(CompleteEnum.class)
+ .setClassDescriptor(descriptor(IncompleteEnum.class))
+ .transform())
+ .addKeepMainRule(TestClass.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("B", "D");
+ }
+
+ private static ClassReference getSwitchMapClassReference() {
+ return Reference.classFromTypeName(SwitchMapWithMissingFieldTest.class.getTypeName() + "$1");
+ }
+
+ private static Path getSwitchMapProgramFile() throws IOException {
+ return getClassFilesForInnerClasses(SwitchMapWithMissingFieldTest.class).stream()
+ .filter(
+ file ->
+ file.toString().endsWith(getSwitchMapClassReference().getBinaryName() + ".class"))
+ .findFirst()
+ .get();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ for (IncompleteEnum value : IncompleteEnum.values()) {
+ switch (value) {
+ case B:
+ System.out.println("B");
+ break;
+
+ case D:
+ System.out.println("D");
+ break;
+ }
+ }
+ }
+ }
+
+ enum CompleteEnum {
+ A,
+ B,
+ C,
+ D,
+ E;
+ }
+
+ enum IncompleteEnum {
+ B,
+ D;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithUnexpectedFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithUnexpectedFieldTest.java
new file mode 100644
index 0000000..0679ef6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithUnexpectedFieldTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2020, 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.switches;
+
+import static com.android.tools.r8.ToolHelper.getClassFilesForInnerClasses;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This tests that we gracefully handle the case where a switch map refers to enum fields that are
+ * not present in the enum definition.
+ *
+ * <p>This situation may happen due to separate compilation. For example, a project may depend on a
+ * library that has been compiled against version X of a given enum but bundle version Y of the
+ * enum.
+ *
+ * <p>See also b/154315490.
+ */
+@RunWith(Parameterized.class)
+public class SwitchMapWithUnexpectedFieldTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SwitchMapWithUnexpectedFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addProgramFiles(getSwitchMapProgramFile())
+ .addProgramClassFileData(
+ transformer(IncompleteEnum.class)
+ .setClassDescriptor(descriptor(CompleteEnum.class))
+ .transform())
+ .addKeepMainRule(TestClass.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("B", "D");
+ }
+
+ private static ClassReference getSwitchMapClassReference() {
+ return Reference.classFromTypeName(SwitchMapWithUnexpectedFieldTest.class.getTypeName() + "$1");
+ }
+
+ private static Path getSwitchMapProgramFile() throws IOException {
+ return getClassFilesForInnerClasses(SwitchMapWithUnexpectedFieldTest.class).stream()
+ .filter(
+ file ->
+ file.toString().endsWith(getSwitchMapClassReference().getBinaryName() + ".class"))
+ .findFirst()
+ .get();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ for (CompleteEnum value : CompleteEnum.values()) {
+ switch (value) {
+ case A:
+ System.out.println("A");
+ break;
+
+ case B:
+ System.out.println("B");
+ break;
+
+ case C:
+ System.out.println("C");
+ break;
+
+ case D:
+ System.out.println("D");
+ break;
+
+ case E:
+ System.out.println("E");
+ break;
+ }
+ }
+ }
+ }
+
+ enum CompleteEnum {
+ A,
+ B,
+ C,
+ D,
+ E;
+ }
+
+ enum IncompleteEnum {
+ B,
+ D;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index e570b8c..bbc6c26 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -59,6 +60,11 @@
}
@Override
+ public Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value) {
+ throw new Unimplemented();
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
throw new Unimplemented();
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
new file mode 100644
index 0000000..c120f9d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertNotNull;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewritePassThroughTest extends KotlinMetadataTestBase {
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ private final TestParameters parameters;
+
+ public MetadataRewritePassThroughTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testKotlinStdLib() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepRules("-keep class kotlin.Metadata")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ public void inspect(CodeInspector inspector) throws IOException, ExecutionException {
+ CodeInspector stdLibInspector = new CodeInspector(ToolHelper.getKotlinStdlibJar());
+ for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) {
+ ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
+ assertThat(r8Clazz, isPresent());
+ KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
+ if (originalMetadata == null) {
+ assertNull(r8Clazz.getKotlinClassMetadata());
+ continue;
+ }
+ assertNotNull(r8Clazz.getKotlinClassMetadata());
+ // TODO(b/152153136): Extend the test with assertions about metadata equality.
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
index 63fe00f..e0d117f 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
@@ -1,3 +1,6 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming.applymapping;
import static com.android.tools.r8.Collectors.toSingle;
diff --git a/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonSwitchTest.java b/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonSwitchTest.java
index 38f1534..97a96cc 100644
--- a/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonSwitchTest.java
@@ -28,7 +28,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public ClassNameComparisonSwitchTest(TestParameters parameters) {
@@ -40,13 +40,8 @@
testForR8(parameters.getBackend())
.addInnerClasses(ClassNameComparisonSwitchTest.class)
.addKeepMainRule(TestClass.class)
- .addOptionsModification(
- options -> {
- assert !options.enableStringSwitchConversion;
- options.enableStringSwitchConversion = true;
- })
.enableInliningAnnotations()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyClassesHaveBeenMinified)
.run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index c67c66c..7b0e410 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.transformers;
+import static com.android.tools.r8.references.Reference.classFromTypeName;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
+import static com.android.tools.r8.utils.StringUtils.replaceAll;
import static org.objectweb.asm.Opcodes.ASM7;
import com.android.tools.r8.TestRuntime.CfVm;
@@ -28,8 +31,10 @@
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
public class ClassFileTransformer {
@@ -114,19 +119,21 @@
// Transformer utilities.
private final byte[] bytes;
+ private final ClassReference classReference;
private final List<ClassTransformer> classTransformers = new ArrayList<>();
private final List<MethodTransformer> methodTransformers = new ArrayList<>();
- private ClassFileTransformer(byte[] bytes) {
+ private ClassFileTransformer(byte[] bytes, ClassReference classReference) {
this.bytes = bytes;
+ this.classReference = classReference;
}
- public static ClassFileTransformer create(byte[] bytes) {
- return new ClassFileTransformer(bytes);
+ public static ClassFileTransformer create(byte[] bytes, ClassReference classReference) {
+ return new ClassFileTransformer(bytes, classReference);
}
public static ClassFileTransformer create(Class<?> clazz) throws IOException {
- return create(ToolHelper.getClassAsBytes(clazz));
+ return create(ToolHelper.getClassAsBytes(clazz), classFromTypeName(clazz.getTypeName()));
}
public byte[] transform() {
@@ -145,6 +152,10 @@
return this;
}
+ public ClassReference getClassReference() {
+ return classReference;
+ }
+
/** Unconditionally replace the implements clause of a class. */
public ClassFileTransformer setImplements(Class<?>... interfaces) {
return addClassTransformer(
@@ -169,29 +180,52 @@
}
});
}
-
+
/** Unconditionally replace the descriptor (ie, qualified name) of a class. */
- public ClassFileTransformer setClassDescriptor(String descriptor) {
- assert DescriptorUtils.isClassDescriptor(descriptor);
+ public ClassFileTransformer setClassDescriptor(String newDescriptor) {
+ assert DescriptorUtils.isClassDescriptor(newDescriptor);
+ String newBinaryName = getBinaryNameFromDescriptor(newDescriptor);
return addClassTransformer(
- new ClassTransformer() {
- @Override
- public void visit(
- int version,
- int access,
- String ignoredName,
- String signature,
- String superName,
- String[] interfaces) {
- super.visit(
- version,
- access,
- DescriptorUtils.getBinaryNameFromDescriptor(descriptor),
- signature,
- superName,
- interfaces);
- }
- });
+ new ClassTransformer() {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String binaryName,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, newBinaryName, signature, superName, interfaces);
+ }
+
+ @Override
+ public FieldVisitor visitField(
+ int access, String name, String descriptor, String signature, Object object) {
+ return super.visitField(
+ access,
+ name,
+ replaceAll(descriptor, getClassReference().getDescriptor(), newDescriptor),
+ signature,
+ object);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ return super.visitMethod(
+ access,
+ name,
+ replaceAll(descriptor, getClassReference().getDescriptor(), newDescriptor),
+ signature,
+ exceptions);
+ }
+ })
+ .replaceClassDescriptorInMethodInstructions(
+ getClassReference().getDescriptor(), newDescriptor);
}
public ClassFileTransformer setVersion(int newVersion) {
@@ -425,6 +459,55 @@
void apply(Label start, Label end, Label handler, String type);
}
+ public ClassFileTransformer replaceClassDescriptorInMethodInstructions(
+ String oldDescriptor, String newDescriptor) {
+ return addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
+ super.visitFieldInsn(
+ opcode,
+ rewriteASMInternalTypeName(owner),
+ name,
+ replaceAll(descriptor, oldDescriptor, newDescriptor));
+ }
+
+ @Override
+ public void visitLdcInsn(Object value) {
+ if (value instanceof Type) {
+ Type type = (Type) value;
+ super.visitLdcInsn(
+ Type.getType(replaceAll(type.getDescriptor(), oldDescriptor, newDescriptor)));
+ } else {
+ super.visitLdcInsn(value);
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(
+ int opcode, String owner, String name, String descriptor, boolean isInterface) {
+ super.visitMethodInsn(
+ opcode,
+ rewriteASMInternalTypeName(owner),
+ name,
+ replaceAll(descriptor, oldDescriptor, newDescriptor),
+ isInterface);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ super.visitTypeInsn(opcode, rewriteASMInternalTypeName(type));
+ }
+
+ private String rewriteASMInternalTypeName(String type) {
+ return Type.getType(
+ replaceAll(
+ Type.getObjectType(type).getDescriptor(), oldDescriptor, newDescriptor))
+ .getInternalName();
+ }
+ });
+ }
+
public ClassFileTransformer transformMethodInsnInMethod(
String methodName, MethodInsnTransform transform) {
return addMethodTransformer(
diff --git a/third_party/android_jar/lib-v30.tar.gz.sha1 b/third_party/android_jar/lib-v30.tar.gz.sha1
new file mode 100644
index 0000000..700ad7c
--- /dev/null
+++ b/third_party/android_jar/lib-v30.tar.gz.sha1
@@ -0,0 +1 @@
+629e0bfb886d247246268130d946678fa1a4ca9e
\ No newline at end of file
diff --git a/tools/linux/dalvik-4.0.4.tar.gz.sha1 b/tools/linux/dalvik-4.0.4.tar.gz.sha1
index bb9ee2f..34da306 100644
--- a/tools/linux/dalvik-4.0.4.tar.gz.sha1
+++ b/tools/linux/dalvik-4.0.4.tar.gz.sha1
@@ -1 +1 @@
-3815b581f86d3fe6a1affcd72bccad03cfaf0caa
\ No newline at end of file
+a338dffed04d76d9be0fb8f1ad49e44225bff311
\ No newline at end of file
diff --git a/tools/linux/dalvik.tar.gz.sha1 b/tools/linux/dalvik.tar.gz.sha1
index 3aea456..95e199d 100644
--- a/tools/linux/dalvik.tar.gz.sha1
+++ b/tools/linux/dalvik.tar.gz.sha1
@@ -1 +1 @@
-8350b21e7d340f44929d40ec26f7610cd45513f7
\ No newline at end of file
+1bc1761c5f62e9e40046acfae9fcb2092bb63c12
\ No newline at end of file
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 502666f..acc70ed 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -277,13 +277,9 @@
subprocess.check_call('g4 open %s' % file, shell=True)
-def g4_add(files):
- subprocess.check_call(' '.join(['g4', 'add'] + files), shell=True)
-
-
-def g4_change(version, r8version):
+def g4_change(version):
return subprocess.check_output(
- 'g4 change --desc "Update R8 to version %s %s\n"' % (version, r8version),
+ 'g4 change --desc "Update R8 to version %s\n"' % (version),
shell=True)
@@ -320,64 +316,29 @@
google3_base = subprocess.check_output(
['p4', 'g4d', '-f', 'update-r8']).rstrip()
third_party_r8 = os.path.join(google3_base, 'third_party', 'java', 'r8')
-
- # Check if new version folder is already created
today = datetime.date.today()
- new_version='v%d%02d%02d' % (today.year, today.month, today.day)
- new_version_path = os.path.join(third_party_r8, new_version)
-
- if os.path.exists(new_version_path):
- shutil.rmtree(new_version_path)
-
- # Remove old version
- old_versions = []
- for name in os.listdir(third_party_r8):
- if os.path.isdir(os.path.join(third_party_r8, name)):
- old_versions.append(name)
- old_versions.sort()
-
- if len(old_versions) >= 2:
- shutil.rmtree(os.path.join(third_party_r8, old_versions[0]))
-
- # Take current version to copy from
- old_version=old_versions[-1]
-
- # Create new version
- assert not os.path.exists(new_version_path)
- os.mkdir(new_version_path)
-
with utils.ChangedWorkingDirectory(third_party_r8):
- g4_cp(old_version, new_version, 'BUILD')
- g4_cp(old_version, new_version, 'LICENSE')
- g4_cp(old_version, new_version, 'METADATA')
+ # download files
+ g4_open('full.jar')
+ g4_open('src.jar')
+ g4_open('lib.jar')
+ g4_open('lib.jar.map')
+ download_file(options.version, 'r8-full-exclude-deps.jar', 'full.jar')
+ download_file(options.version, 'r8-src.jar', 'src.jar')
+ download_file(options.version, 'r8lib-exclude-deps.jar', 'lib.jar')
+ download_file(
+ options.version, 'r8lib-exclude-deps.jar.map', 'lib.jar.map')
+ g4_open('METADATA')
+ sed(r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev',
+ options.version,
+ os.path.join(third_party_r8, 'METADATA'))
+ sed(r'\{ year.*\}',
+ ('{ year: %i month: %i day: %i }'
+ % (today.year, today.month, today.day)),
+ os.path.join(third_party_r8, 'METADATA'))
- with utils.ChangedWorkingDirectory(new_version_path):
- # update METADATA
- g4_open('METADATA')
- sed(r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev',
- options.version,
- os.path.join(new_version_path, 'METADATA'))
- sed(r'\{ year.*\}',
- ('{ year: %i month: %i day: %i }'
- % (today.year, today.month, today.day))
- , os.path.join(new_version_path, 'METADATA'))
- # update BUILD (is not necessary from v20190923)
- g4_open('BUILD')
- sed(old_version, new_version, os.path.join(new_version_path, 'BUILD'))
-
- # download files
- download_file(options.version, 'r8-full-exclude-deps.jar', 'r8.jar')
- download_file(options.version, 'r8-src.jar', 'r8-src.jar')
- download_file(options.version, 'r8lib-exclude-deps.jar', 'r8lib.jar')
- download_file(
- options.version, 'r8lib-exclude-deps.jar.map', 'r8lib.jar.map')
- g4_add(['r8.jar', 'r8-src.jar', 'r8lib.jar', 'r8lib.jar.map'])
-
- subprocess.check_output('chmod u+w %s/*' % new_version, shell=True)
-
- g4_open('BUILD')
- sed(old_version, new_version, os.path.join(third_party_r8, 'BUILD'))
+ subprocess.check_output('chmod u+w *', shell=True)
with utils.ChangedWorkingDirectory(google3_base):
blaze_result = blaze_run('//third_party/java/r8:d8 -- --version')
@@ -385,7 +346,7 @@
assert options.version in blaze_result
if not options.no_upload:
- return g4_change(new_version, options.version)
+ return g4_change(options.version)
return release_google3
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 9182539..6d7905e 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -12,6 +12,7 @@
import os
import optparse
import shutil
+import signal
import subprocess
import sys
import time
@@ -415,6 +416,9 @@
})
]
+def signal_handler(signum, frame):
+ subprocess.call(['pkill', 'java'])
+ raise Exception('Got killed by %s' % signum)
class EnsureNoGradleAlive(object):
def __init__(self, active):
@@ -422,6 +426,8 @@
def __enter__(self):
if self.active:
+ # If we timeout and get a sigterm we should still kill all java
+ signal.signal(signal.SIGTERM, signal_handler)
print 'Running with wrapper that will kill java after'
def __exit__(self, *_):