Merge commit 'b3e71c33f5f9f60c893ddcbc62a9c08f428dfccf' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java index fda08a6..f66fff8 100644 --- a/src/main/java/com/android/tools/r8/D8CommandParser.java +++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -111,8 +111,9 @@ "\n", Iterables.concat( Arrays.asList( - "Usage: d8 [options] <input-files>", + "Usage: d8 [options] [@<argfile>] <input-files>", " where <input-files> are any combination of dex, class, zip, jar, or apk files", + " and each <argfile> is a file containing additional arguments (one per line)", " and options are:", " --debug # Compile with debugging information (default).", " --release # Compile without debugging information.", @@ -258,6 +259,8 @@ builder.error(new StringDiagnostic("Unknown option: " + arg, origin)); continue; } + } else if (arg.startsWith("@")) { + builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin)); } else { builder.addProgramFiles(Paths.get(arg)); }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 7db3d7c..75f862e 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -35,7 +35,6 @@ import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense; import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring; import com.android.tools.r8.ir.optimize.AssertionsRewriter; -import com.android.tools.r8.ir.optimize.EnumInfoMapCollector; import com.android.tools.r8.ir.optimize.MethodPoolCollection; import com.android.tools.r8.ir.optimize.NestReducer; import com.android.tools.r8.ir.optimize.SwitchMapCollector; @@ -43,6 +42,7 @@ import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense; import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector; import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense; +import com.android.tools.r8.ir.optimize.enums.EnumInfoMapCollector; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.jar.CfApplicationWriter; import com.android.tools.r8.kotlin.Kotlin; @@ -308,7 +308,7 @@ // Compute kotlin info before setting the roots and before // kotlin metadata annotation is removed. - computeKotlinInfoForProgramClasses(application, appView); + computeKotlinInfoForProgramClasses(application, appView, executorService); // Add synthesized -assumenosideeffects from min api if relevant. if (options.isGeneratingDex()) { @@ -508,6 +508,8 @@ // Collect switch maps and ordinals maps. if (options.enableEnumValueOptimization) { appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness).run()); + } + if (options.enableEnumValueOptimization || options.enableEnumUnboxing) { appViewWithLiveness.setAppInfo(new EnumInfoMapCollector(appViewWithLiveness).run()); } @@ -725,7 +727,8 @@ // Rewrite signature annotations for applications that are not minified. if (appView.appInfo().hasLiveness()) { // TODO(b/124726014): Rewrite signature annotations in lens rewriting instead of here? - new GenericSignatureRewriter(appView.withLiveness()).run(appView.appInfo().classes()); + new GenericSignatureRewriter(appView.withLiveness()) + .run(appView.appInfo().classes(), executorService); } namingLens = NamingLens.getIdentityLens(); } @@ -884,17 +887,23 @@ throw new CompilationError("Discard checks failed."); } - private void computeKotlinInfoForProgramClasses(DexApplication application, AppView<?> appView) { + private void computeKotlinInfoForProgramClasses( + DexApplication application, AppView<?> appView, ExecutorService executorService) + throws ExecutionException{ if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) { return; } Kotlin kotlin = appView.dexItemFactory().kotlin; Reporter reporter = options.reporter; - for (DexProgramClass programClass : application.classes()) { - KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter); - programClass.setKotlinInfo(kotlinInfo); - KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter); - } + ThreadUtils.processItems( + application.classes(), + programClass -> { + KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter); + programClass.setKotlinInfo(kotlinInfo); + KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter); + }, + executorService + ); } private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java index f1541bd..22ebb5e 100644 --- a/src/main/java/com/android/tools/r8/R8CommandParser.java +++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -52,8 +52,9 @@ "\n", Iterables.concat( Arrays.asList( - "Usage: r8 [options] <input-files>", + "Usage: r8 [options] [@<argfile>] <input-files>", " where <input-files> are any combination of dex, class, zip, jar, or apk files", + " and each <argfile> is a file containing additional arguments (one per line)", " and options are:", " --release # Compile without debugging information (default).", " --debug # Compile with debugging information.", @@ -223,6 +224,8 @@ builder.error(new StringDiagnostic("Unknown option: " + arg, argsOrigin)); continue; } + } else if (arg.startsWith("@")) { + builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", argsOrigin)); } else { builder.addProgramFiles(Paths.get(arg)); }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java index c69d8e0..df91f96 100644 --- a/src/main/java/com/android/tools/r8/dex/DexParser.java +++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -14,7 +14,6 @@ import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.graph.ClassAccessFlags; import com.android.tools.r8.graph.ClassKind; -import com.android.tools.r8.graph.Descriptor; import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexAnnotationElement; import com.android.tools.r8.graph.DexAnnotationSet; @@ -33,6 +32,7 @@ import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItem; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMember; import com.android.tools.r8.graph.DexMemberAnnotation; import com.android.tools.r8.graph.DexMemberAnnotation.DexFieldAnnotation; import com.android.tools.r8.graph.DexMemberAnnotation.DexMethodAnnotation; @@ -570,21 +570,21 @@ return new DexDebugInfo(start, parameters, events.toArray(DexDebugEvent.EMPTY_ARRAY)); } - private static class MemberAnnotationIterator<S extends Descriptor<?, S>, T extends DexItem> { + private static class MemberAnnotationIterator<R extends DexMember<?, R>, T extends DexItem> { private int index = 0; - private final DexMemberAnnotation<S, T>[] annotations; + private final DexMemberAnnotation<R, T>[] annotations; private final Supplier<T> emptyValue; - private MemberAnnotationIterator(DexMemberAnnotation<S, T>[] annotations, - Supplier<T> emptyValue) { + private MemberAnnotationIterator( + DexMemberAnnotation<R, T>[] annotations, Supplier<T> emptyValue) { this.annotations = annotations; this.emptyValue = emptyValue; } // Get the annotation set for an item. This method assumes that it is always called with // an item that is higher in the sorting order than the last item. - T getNextFor(S item) { + T getNextFor(R item) { // TODO(ager): We could use the indices from the file to make this search faster using // compareTo instead of slowCompareTo. That would require us to assign indices during // reading. Those indices should be cleared after reading to make sure that we resort @@ -1189,7 +1189,7 @@ MethodHandleType type = MethodHandleType.getKind(dexReader.getUshort()); dexReader.getUshort(); // unused int indexFieldOrMethod = dexReader.getUshort(); - Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod; + DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod; switch (type) { case INSTANCE_GET: case INSTANCE_PUT:
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java index 53f65b6..8b0cd00 100644 --- a/src/main/java/com/android/tools/r8/dex/FileWriter.java +++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -9,7 +9,6 @@ import com.android.tools.r8.ByteBufferProvider; import com.android.tools.r8.code.Instruction; import com.android.tools.r8.errors.CompilationError; -import com.android.tools.r8.graph.Descriptor; import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexAnnotationDirectory; import com.android.tools.r8.graph.DexAnnotationElement; @@ -29,6 +28,7 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItem; +import com.android.tools.r8.graph.DexMember; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexMethodHandle; import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType; @@ -577,10 +577,10 @@ } } - private <S extends Descriptor<T, S>, T extends DexEncodedMember<S>> void writeMemberAnnotations( - List<T> items, ToIntFunction<T> getter) { - for (T item : items) { - dest.putInt(item.getKey().getOffset(mapping)); + private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void writeMemberAnnotations( + List<D> items, ToIntFunction<D> getter) { + for (D item : items) { + dest.putInt(item.toReference().getOffset(mapping)); dest.putInt(getter.applyAsInt(item)); } }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java index c709a31..a6a9471 100644 --- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -150,6 +150,10 @@ return visibilityOrdinal() == 1 || visibilityOrdinal() == 2; } + public boolean isPackagePrivate() { + return !isPublic() && !isPrivate() && !isProtected(); + } + public boolean isPublic() { return isSet(Constants.ACC_PUBLIC); }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java index 29a5995..141f742 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfo.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -29,8 +29,8 @@ private final DexApplication app; private final DexItemFactory dexItemFactory; - private final ConcurrentHashMap<DexType, Map<Descriptor<?, ?>, DexEncodedMember<?>>> definitions = - new ConcurrentHashMap<>(); + private final ConcurrentHashMap<DexType, Map<DexMember<?, ?>, DexEncodedMember<?, ?>>> + definitions = new ConcurrentHashMap<>(); // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current // class being optimized. final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses = new ConcurrentHashMap<>(); @@ -101,12 +101,12 @@ return Collections.unmodifiableCollection(synthesizedClasses.values()); } - private Map<Descriptor<?, ?>, DexEncodedMember<?>> computeDefinitions(DexType type) { - Builder<Descriptor<?, ?>, DexEncodedMember<?>> builder = ImmutableMap.builder(); + private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> computeDefinitions(DexType type) { + Builder<DexMember<?, ?>, DexEncodedMember<?, ?>> builder = ImmutableMap.builder(); DexClass clazz = definitionFor(type); if (clazz != null) { - clazz.forEachMethod(method -> builder.put(method.getKey(), method)); - clazz.forEachField(field -> builder.put(field.getKey(), field)); + clazz.forEachMethod(method -> builder.put(method.toReference(), method)); + clazz.forEachField(field -> builder.put(field.toReference(), field)); } return builder.build(); } @@ -186,14 +186,14 @@ return (DexEncodedField) getDefinitions(field.holder).get(field); } - private Map<Descriptor<?, ?>, DexEncodedMember<?>> getDefinitions(DexType type) { - Map<Descriptor<?, ?>, DexEncodedMember<?>> typeDefinitions = definitions.get(type); + private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> getDefinitions(DexType type) { + Map<DexMember<?, ?>, DexEncodedMember<?, ?>> typeDefinitions = definitions.get(type); if (typeDefinitions != null) { return typeDefinitions; } typeDefinitions = computeDefinitions(type); - Map<Descriptor<?, ?>, DexEncodedMember<?>> existing = + Map<DexMember<?, ?>, DexEncodedMember<?, ?>> existing = definitions.putIfAbsent(type, typeDefinitions); return existing != null ? existing : typeDefinitions; }
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 495c0b3..813ea84 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -7,6 +7,8 @@ import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement; import com.android.tools.r8.utils.SetUtils; +import com.android.tools.r8.utils.WorkList; +import com.android.tools.r8.utils.WorkList.EqualityTest; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -20,11 +22,13 @@ import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; -import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.function.Consumer; import java.util.function.Function; -public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy implements LiveSubTypeInfo { +public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy + implements InstantiatedSubTypeInfo { private static final int ROOT_LEVEL = 0; private static final int UNKNOWN_LEVEL = -1; @@ -34,16 +38,23 @@ private static final Set<DexType> NO_DIRECT_SUBTYPE = ImmutableSet.of(); @Override - public LiveSubTypeResult getLiveSubTypes(DexType type) { - // TODO(b/139464956): Remove this when we start to have live type information in the enqueuer. - Set<DexProgramClass> programClasses = new HashSet<>(); - for (DexType subtype : subtypes(type)) { - DexProgramClass subClass = definitionForProgramType(subtype); - if (subClass != null) { - programClasses.add(subClass); + public void forEachInstantiatedSubType( + DexType type, + Consumer<DexProgramClass> subTypeConsumer, + Consumer<DexCallSite> callSiteConsumer) { + WorkList<DexType> workList = new WorkList<>(EqualityTest.IDENTITY); + workList.addIfNotSeen(type); + workList.addIfNotSeen(allImmediateSubtypes(type)); + while (workList.hasNext()) { + DexType subType = workList.next(); + DexProgramClass clazz = definitionForProgramType(subType); + if (clazz != null) { + subTypeConsumer.accept(clazz); } + workList.addIfNotSeen(allImmediateSubtypes(subType)); } - return new LiveSubTypeResult(programClasses, null); + // TODO(b/148769279): Change this when we have information about callsites. + callSiteConsumer.accept(null); } private static class TypeInfo { @@ -71,7 +82,7 @@ private void ensureDirectSubTypeSet() { if (directSubtypes == NO_DIRECT_SUBTYPE) { - directSubtypes = new TreeSet<>(DexType::slowCompareTo); + directSubtypes = new ConcurrentSkipListSet<>(DexType::slowCompareTo); } }
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 9f7b4cf..1f24d35 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.ir.analysis.value.AbstractValueFactory; import com.android.tools.r8.ir.desugar.PrefixRewritingMapper; import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory; import com.android.tools.r8.ir.optimize.library.LibraryMethodOptimizer; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.RootSetBuilder.RootSet; @@ -45,6 +46,8 @@ private final InternalOptions options; private RootSet rootSet; private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory(); + private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory = + new InstanceFieldInitializationInfoFactory(); // Desugared library prefix rewriter. public final PrefixRewritingMapper rewritePrefix; @@ -125,6 +128,10 @@ return abstractValueFactory; } + public InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory() { + return instanceFieldInitializationInfoFactory; + } + public T appInfo() { return appInfo; } @@ -149,6 +156,10 @@ allCodeProcessed = true; } + public GraphLense clearCodeRewritings() { + return graphLense = graphLense.withCodeRewritingsApplied(); + } + public AppServices appServices() { return appServices; }
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java index 18f8fe6..a05883a 100644 --- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -8,7 +8,6 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import java.util.IdentityHashMap; -import java.util.List; import java.util.Map; /** @@ -29,7 +28,7 @@ new IdentityHashMap<>(); public AppliedGraphLens( - AppView<? extends AppInfoWithSubtyping> appView, List<DexProgramClass> classes) { + AppView<? extends AppInfoWithSubtyping> appView, Iterable<DexProgramClass> classes) { this.appView = appView; for (DexProgramClass clazz : classes) { @@ -135,4 +134,9 @@ public boolean isContextFreeForMethods() { return true; } + + @Override + public boolean hasCodeRewritings() { + return false; + } }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java index cbc8303..5c8de46 100644 --- a/src/main/java/com/android/tools/r8/graph/CfCode.java +++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -469,7 +469,7 @@ } else { continue; } - if (index >= 0) { + if (index >= 0 && indexToNumber.containsKey(index)) { registry.register(indexToNumber.get(index)); } }
diff --git a/src/main/java/com/android/tools/r8/graph/Descriptor.java b/src/main/java/com/android/tools/r8/graph/Descriptor.java deleted file mode 100644 index f0ba1b6..0000000 --- a/src/main/java/com/android/tools/r8/graph/Descriptor.java +++ /dev/null
@@ -1,22 +0,0 @@ -// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.graph; - -public abstract class Descriptor<T extends DexItem, S extends Descriptor<T,S>> - extends DexReference implements PresortedComparable<S> { - - public abstract boolean match(S entry); - - public abstract boolean match(T entry); - - @Override - public boolean isDescriptor() { - return true; - } - - @Override - public Descriptor asDescriptor() { - return this; - } -}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java index e616e9e..2d0667b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java +++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -107,9 +107,9 @@ throw new Unreachable(); } - private static <T extends PresortedComparable<T>> boolean isSorted( - List<? extends DexEncodedMember<T>> items) { - return isSorted(items, DexEncodedMember::getKey); + private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted( + List<D> items) { + return isSorted(items, DexEncodedMember::toReference); } private static <S, T extends Comparable<T>> boolean isSorted(
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 3e1a144..3601754 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -129,7 +129,7 @@ Iterables.filter(Arrays.asList(staticFields), predicate::test)); } - public Iterable<DexEncodedMember<?>> members() { + public Iterable<DexEncodedMember<?, ?>> members() { return Iterables.concat(fields(), methods()); } @@ -611,8 +611,9 @@ && method.method.proto.parameters.values[0] != factory.objectArrayType; } - private <T extends DexItem, S extends Descriptor<T, S>> T lookupTarget(T[] items, S descriptor) { - for (T entry : items) { + private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> D lookupTarget( + D[] items, R descriptor) { + for (D entry : items) { if (descriptor.match(entry)) { return entry; } @@ -773,11 +774,15 @@ } public boolean classInitializationMayHaveSideEffects(AppView<?> appView) { - return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse()); + return classInitializationMayHaveSideEffects( + appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet()); } public boolean classInitializationMayHaveSideEffects( - AppView<?> appView, Predicate<DexType> ignore) { + AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) { + if (!seen.add(type)) { + return false; + } if (ignore.test(type)) { return false; } @@ -795,7 +800,7 @@ if (defaultValuesForStaticFieldsMayTriggerAllocation()) { return true; } - return initializationOfParentTypesMayHaveSideEffects(appView, ignore); + return initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen); } private boolean hasClassInitializerThatCannotBePostponed() { @@ -820,17 +825,19 @@ } public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) { - return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse()); + return initializationOfParentTypesMayHaveSideEffects( + appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet()); } public boolean initializationOfParentTypesMayHaveSideEffects( - AppView<?> appView, Predicate<DexType> ignore) { + AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) { for (DexType iface : interfaces.values) { - if (iface.classInitializationMayHaveSideEffects(appView, ignore)) { + if (iface.classInitializationMayHaveSideEffects(appView, ignore, seen)) { return true; } } - if (superType != null && superType.classInitializationMayHaveSideEffects(appView, ignore)) { + if (superType != null + && superType.classInitializationMayHaveSideEffects(appView, ignore, seen)) { return true; } return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java new file mode 100644 index 0000000..281b58a --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -0,0 +1,53 @@ +// 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.graph; + +import com.android.tools.r8.errors.Unreachable; + +public class DexClassAndMethod { + + private final DexClass holder; + private final DexEncodedMethod method; + + protected DexClassAndMethod(DexClass holder, DexEncodedMethod method) { + assert holder.type == method.method.holder; + this.holder = holder; + this.method = method; + } + + public static DexClassAndMethod create(DexClass holder, DexEncodedMethod method) { + if (holder.isProgramClass()) { + return new ProgramMethod(holder.asProgramClass(), method); + } else { + return new DexClassAndMethod(holder, method); + } + } + + @Override + public boolean equals(Object obj) { + throw new Unreachable("Unsupported attempt at comparing Class and DexClassAndMethod"); + } + + @Override + public int hashCode() { + throw new Unreachable("Unsupported attempt at computing the hashcode of DexClassAndMethod"); + } + + public DexClass getHolder() { + return holder; + } + + public DexEncodedMethod getMethod() { + return method; + } + + public boolean isProgramMethod() { + return false; + } + + public ProgramMethod asProgramMethod() { + return null; + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java index dbf18c3..7f6796d 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java +++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -51,7 +51,7 @@ return false; } - public DexEncodedMember<?> asDexEncodedMember() { + public DexEncodedMember<?, ?> asDexEncodedMember() { return null; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java index 4f332f9..ce89a41 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -18,8 +18,9 @@ import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo; import com.android.tools.r8.kotlin.KotlinMemberInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.Sets; -public class DexEncodedField extends DexEncodedMember<DexField> { +public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField> { public static final DexEncodedField[] EMPTY_ARRAY = {}; public final DexField field; @@ -110,12 +111,7 @@ } @Override - public DexField getKey() { - return field; - } - - @Override - public DexReference toReference() { + public DexField toReference() { return field; } @@ -222,7 +218,8 @@ appView, // Types that are a super type of the current context are guaranteed to be initialized // already. - type -> appView.isSubtype(context, type).isTrue())) { + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet())) { // Ignore class initialization side-effects for dead proto extension fields to ensure that // we force replace these field reads by null. boolean ignore =
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java index 6987227..0e436d6 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -3,13 +3,15 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; -public abstract class DexEncodedMember<T extends PresortedComparable<T>> extends DexDefinition { +public abstract class DexEncodedMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> + extends DexDefinition { public DexEncodedMember(DexAnnotationSet annotations) { super(annotations); } - public abstract T getKey(); + @Override + public abstract R toReference(); @Override public boolean isDexEncodedMember() { @@ -17,7 +19,7 @@ } @Override - public DexEncodedMember<?> asDexEncodedMember() { + public DexEncodedMember<D, R> asDexEncodedMember() { return this; } @@ -27,11 +29,11 @@ return true; } return other.getClass() == getClass() - && ((DexEncodedMember<?>) other).getKey().equals(getKey()); + && ((DexEncodedMember<?, ?>) other).toReference().equals(toReference()); } @Override public final int hashCode() { - return getKey().hashCode(); + return toReference().hashCode(); } }
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 184e15e..b755c49 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -76,7 +76,7 @@ import java.util.function.IntPredicate; import org.objectweb.asm.Opcodes; -public class DexEncodedMethod extends DexEncodedMember<DexMethod> { +public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod> { public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in "; @@ -1163,14 +1163,7 @@ } @Override - public DexMethod getKey() { - // Here, we can't check if the current instance of DexEncodedMethod is obsolete - // because itself can be used as a key while making mappings to avoid using obsolete instances. - return method; - } - - @Override - public DexReference toReference() { + public DexMethod toReference() { checkIfObsolete(); return method; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java index dfd3203..02cb985 100644 --- a/src/main/java/com/android/tools/r8/graph/DexField.java +++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -7,15 +7,13 @@ import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.naming.NamingLens; -public class DexField extends Descriptor<DexEncodedField, DexField> implements - PresortedComparable<DexField> { +public class DexField extends DexMember<DexEncodedField, DexField> { - public final DexType holder; public final DexType type; public final DexString name; DexField(DexType holder, DexType type, DexString name, boolean skipNameValidationForTesting) { - this.holder = holder; + super(holder); this.type = type; this.name = name; if (!skipNameValidationForTesting && !name.isValidFieldName()) {
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 df2402f..8cb71bb 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1539,7 +1539,7 @@ public DexMethodHandle createMethodHandle( MethodHandleType type, - Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod, + DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod, boolean isInterface) { assert !sorted; DexMethodHandle methodHandle = new DexMethodHandle(type, fieldOrMethod, isInterface);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java new file mode 100644 index 0000000..96afd57 --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -0,0 +1,28 @@ +// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.graph; + +public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> + extends DexReference implements PresortedComparable<R> { + + public final DexType holder; + + public DexMember(DexType holder) { + this.holder = holder; + } + + public abstract boolean match(R entry); + + public abstract boolean match(D entry); + + @Override + public boolean isDexMember() { + return true; + } + + @Override + public DexMember<D, R> asDexMember() { + return this; + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java index 6cc2fb4..d9868a5 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java +++ b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
@@ -6,12 +6,12 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; -public class DexMemberAnnotation<T extends Descriptor<?,?>, S extends DexItem> extends DexItem { +public class DexMemberAnnotation<R extends DexMember<?, R>, S extends DexItem> extends DexItem { - public final T item; + public final R item; public final S annotations; - public DexMemberAnnotation(T item, S annotations) { + public DexMemberAnnotation(R item, S annotations) { this.item = item; this.annotations = annotations; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java index a1e98f7..a29eb31 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -9,10 +9,8 @@ import com.google.common.collect.Maps; import java.util.Map; -public class DexMethod extends Descriptor<DexEncodedMethod, DexMethod> - implements PresortedComparable<DexMethod> { +public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> { - public final DexType holder; public final DexProto proto; public final DexString name; @@ -20,7 +18,7 @@ private Map<DexType, DexEncodedMethod> singleTargetCache; DexMethod(DexType holder, DexProto proto, DexString name, boolean skipNameValidationForTesting) { - this.holder = holder; + super(holder); this.proto = proto; this.name = name; if (!skipNameValidationForTesting && !name.isValidMethodName()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java index 336842f..fbe8a9f 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -187,7 +187,7 @@ public final MethodHandleType type; // Field or method that the method handle is targeting. - public final Descriptor<? extends DexItem, ? extends Descriptor<?,?>> fieldOrMethod; + public final DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod; public final boolean isInterface; @@ -204,7 +204,7 @@ public DexMethodHandle( MethodHandleType type, - Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod, + DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod, boolean isInterface) { this.type = type; this.fieldOrMethod = fieldOrMethod; @@ -214,7 +214,7 @@ public DexMethodHandle( MethodHandleType type, - Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod, + DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod, boolean isInterface, DexMethod rewrittenTarget) { this.type = type; @@ -226,7 +226,7 @@ public static DexMethodHandle fromAsmHandle( Handle handle, JarApplicationReader application, DexType clazz) { MethodHandleType methodHandleType = MethodHandleType.fromAsmHandle(handle, application, clazz); - Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> descriptor = + DexMember<? extends DexItem, ? extends DexMember<?, ?>> descriptor = methodHandleType.isFieldType() ? application.getField(handle.getOwner(), handle.getName(), handle.getDesc()) : application.getMethod(handle.getOwner(), handle.getName(), handle.getDesc());
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java index 1ac7c58..78a59c2 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -352,11 +352,11 @@ && isSorted(instanceFields); } - private static <T extends DexEncodedMember<S>, S extends PresortedComparable<S>> boolean isSorted( - T[] items) { + private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted( + D[] items) { synchronized (items) { - T[] sorted = items.clone(); - Arrays.sort(sorted, Comparator.comparing(DexEncodedMember::getKey)); + D[] sorted = items.clone(); + Arrays.sort(sorted, Comparator.comparing(DexEncodedMember::toReference)); return Arrays.equals(items, sorted); } } @@ -441,7 +441,7 @@ * entire scope. */ public boolean hasReachabilitySensitiveAnnotation(DexItemFactory factory) { - for (DexEncodedMember<?> member : members()) { + for (DexEncodedMember<?, ?> member : members()) { for (DexAnnotation annotation : member.annotations().annotations) { if (annotation.annotation.type == factory.annotationReachabilitySensitive) { return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java index d51810e..42286db 100644 --- a/src/main/java/com/android/tools/r8/graph/DexReference.java +++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -21,11 +21,11 @@ return null; } - public boolean isDescriptor() { + public boolean isDexMember() { return false; } - public Descriptor asDescriptor() { + public DexMember<?, ?> asDexMember() { return null; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java index 9a8a73c..08d4d61 100644 --- a/src/main/java/com/android/tools/r8/graph/DexType.java +++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -25,9 +25,11 @@ import com.android.tools.r8.utils.Pair; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Predicate; public class DexType extends DexReference implements PresortedComparable<DexType> { @@ -60,23 +62,26 @@ } public boolean classInitializationMayHaveSideEffects(AppView<?> appView) { - return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse()); + return classInitializationMayHaveSideEffects( + appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet()); } public boolean classInitializationMayHaveSideEffects( - AppView<?> appView, Predicate<DexType> ignore) { + AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) { DexClass clazz = appView.definitionFor(this); - return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore); + return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore, seen); } public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) { - return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse()); + return initializationOfParentTypesMayHaveSideEffects( + appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet()); } public boolean initializationOfParentTypesMayHaveSideEffects( - AppView<?> appView, Predicate<DexType> ignore) { + AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) { DexClass clazz = appView.definitionFor(this); - return clazz == null || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore); + return clazz == null + || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen); } public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java index f1d1a34..e01f0ee 100644 --- a/src/main/java/com/android/tools/r8/graph/GraphLense.java +++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -223,10 +223,21 @@ return IdentityGraphLense.getInstance(); } + public boolean hasCodeRewritings() { + return true; + } + public final boolean isIdentityLense() { return this == getIdentityLense(); } + public GraphLense withCodeRewritingsApplied() { + if (hasCodeRewritings()) { + return new ClearCodeRewritingGraphLens(this); + } + return this; + } + public <T extends DexDefinition> boolean assertDefinitionsNotModified(Iterable<T> definitions) { for (DexDefinition definition : definitions) { DexReference reference = definition.toReference(); @@ -455,6 +466,51 @@ public boolean isContextFreeForMethods() { return true; } + + @Override + public boolean hasCodeRewritings() { + return false; + } + } + + // This lens clears all code rewriting (lookup methods mimics identity lens behavior) but still + // relies on the previous lens for names (getRenamed/Original methods). + public static class ClearCodeRewritingGraphLens extends IdentityGraphLense { + private final GraphLense previous; + + public ClearCodeRewritingGraphLens(GraphLense previous) { + this.previous = previous; + } + + @Override + public DexType getOriginalType(DexType type) { + return previous.getOriginalType(type); + } + + @Override + public DexField getOriginalFieldSignature(DexField field) { + return previous.getOriginalFieldSignature(field); + } + + @Override + public DexMethod getOriginalMethodSignature(DexMethod method) { + return previous.getOriginalMethodSignature(method); + } + + @Override + public DexField getRenamedFieldSignature(DexField originalField) { + return previous.getRenamedFieldSignature(originalField); + } + + @Override + public DexMethod getRenamedMethodSignature(DexMethod originalMethod) { + return previous.getRenamedMethodSignature(originalMethod); + } + + @Override + public DexType lookupType(DexType type) { + return previous.lookupType(type); + } } /**
diff --git a/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java b/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java new file mode 100644 index 0000000..e4942bb --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java
@@ -0,0 +1,16 @@ +// 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.graph; + +import java.util.function.Consumer; + +@FunctionalInterface +public interface InstantiatedSubTypeInfo { + + void forEachInstantiatedSubType( + DexType type, + Consumer<DexProgramClass> subTypeConsumer, + Consumer<DexCallSite> callSiteConsumer); +}
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java index 27f4c9c..8b5977f 100644 --- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java +++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -104,7 +104,7 @@ public DexMethodHandle getMethodHandle( MethodHandleType type, - Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod, + DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod, boolean isInterface) { return options.itemFactory.createMethodHandle(type, fieldOrMethod, isInterface); }
diff --git a/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java b/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java deleted file mode 100644 index f812d7e..0000000 --- a/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java +++ /dev/null
@@ -1,33 +0,0 @@ -// 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.graph; - -import java.util.Set; - -@FunctionalInterface -public interface LiveSubTypeInfo { - - LiveSubTypeResult getLiveSubTypes(DexType type); - - class LiveSubTypeResult { - - private final Set<DexProgramClass> programClasses; - private final Set<DexCallSite> callSites; - - public LiveSubTypeResult(Set<DexProgramClass> programClasses, Set<DexCallSite> callSites) { - // TODO(b/149006127): Enable when we no longer rely on callSites == null. - this.programClasses = programClasses; - this.callSites = callSites; - } - - public Set<DexProgramClass> getProgramClasses() { - return programClasses; - } - - public Set<DexCallSite> getCallSites() { - return callSites; - } - } -}
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 fa13c74..af2bdd4 100644 --- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java +++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -33,6 +33,11 @@ return new Builder(trackAllocationSites, reporter); } + public void markNoLongerInstantiated(DexProgramClass clazz) { + classesWithAllocationSiteTracking.remove(clazz); + classesWithoutAllocationSiteTracking.remove(clazz); + } + @Override public void forEachClassWithKnownAllocationSites( BiConsumer<DexProgramClass, Set<DexEncodedMethod>> consumer) {
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java index 81e6d56..075c187 100644 --- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java +++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -10,9 +10,9 @@ public interface PresortedComparable<T> extends Presorted, Comparable<T> { - static <T extends PresortedComparable<T>> boolean isSorted( - List<? extends DexEncodedMember<T>> items) { - return isSorted(items, DexEncodedMember::getKey); + static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted( + List<? extends DexEncodedMember<D, R>> items) { + return isSorted(items, DexEncodedMember::toReference); } static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java index 1e33e66..4924d0c 100644 --- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java +++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -3,26 +3,27 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; -import com.android.tools.r8.errors.Unreachable; - /** Type representing a method definition in the programs compilation unit and its holder. */ -public class ProgramMethod { - public final DexProgramClass holder; - public final DexEncodedMethod method; +public final class ProgramMethod extends DexClassAndMethod { - public ProgramMethod(DexProgramClass holder, DexEncodedMethod method) { - assert holder.type == method.method.holder; - this.holder = holder; - this.method = method; + public ProgramMethod(DexProgramClass holder, DexEncodedMethod method) { + super(holder, method); } @Override - public boolean equals(Object obj) { - throw new Unreachable("Unsupported attempt at comparing ProgramMethod"); + public boolean isProgramMethod() { + return true; } @Override - public int hashCode() { - throw new Unreachable("Unsupported attempt at computing the hashcode of ProgramMethod"); + public ProgramMethod asProgramMethod() { + return this; + } + + @Override + public DexProgramClass getHolder() { + DexClass holder = super.getHolder(); + assert holder.isProgramClass(); + return holder.asProgramClass(); } }
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java index c2bd3a4..feffac7 100644 --- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java +++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -4,10 +4,8 @@ package com.android.tools.r8.graph; import com.android.tools.r8.errors.CompilationError; -import com.android.tools.r8.graph.LiveSubTypeInfo.LiveSubTypeResult; import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.SetUtils; import com.google.common.collect.Sets; import java.util.Collection; import java.util.Collections; @@ -79,12 +77,18 @@ DexProgramClass context, AppInfoWithClassHierarchy appInfo); public abstract LookupResult lookupVirtualDispatchTargets( - AppView<?> appView, LiveSubTypeInfo liveSubTypes); + DexProgramClass context, + AppView<? extends AppInfoWithClassHierarchy> appView, + InstantiatedSubTypeInfo instantiatedInfo); - public final LookupResult lookupVirtualDispatchTargets(AppView<AppInfoWithLiveness> appView) { - return lookupVirtualDispatchTargets(appView, appView.appInfo()); + public final LookupResult lookupVirtualDispatchTargets( + DexProgramClass context, AppView<AppInfoWithLiveness> appView) { + return lookupVirtualDispatchTargets(context, appView, appView.appInfo()); } + public abstract DexClassAndMethod lookupVirtualDispatchTarget( + DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView); + /** Result for a resolution that succeeds with a known declaration/definition. */ public static class SingleResolutionResult extends ResolutionResult { private final DexClass initialResolutionHolder; @@ -102,6 +106,8 @@ this.resolvedHolder = resolvedHolder; this.resolvedMethod = resolvedMethod; this.initialResolutionHolder = initialResolutionHolder; + assert !resolvedMethod.isPrivateMethod() + || initialResolutionHolder.type == resolvedMethod.method.holder; } public DexClass getResolvedHolder() { @@ -331,132 +337,186 @@ @Override public LookupResult lookupVirtualDispatchTargets( - AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) { - return initialResolutionHolder.isInterface() - ? lookupInterfaceTargets(appView, liveSubTypeInfo) - : lookupVirtualTargets(appView, liveSubTypeInfo); - } - - // TODO(b/140204899): Leverage refined receiver type if available. - private LookupResult lookupVirtualTargets(AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) { - assert !initialResolutionHolder.isInterface(); + DexProgramClass context, + AppView<? extends AppInfoWithClassHierarchy> appView, + InstantiatedSubTypeInfo instantiatedInfo) { + // Check that the initial resolution holder is accessible from the context. + if (context != null && !isAccessibleFrom(context, appView.appInfo())) { + return LookupResult.createFailedResult(); + } if (resolvedMethod.isPrivateMethod()) { // If the resolved reference is private there is no dispatch. // This is assuming that the method is accessible, which implies self/nest access. - return LookupResult.createResult( - Collections.singleton(resolvedMethod), LookupResultCollectionState.Complete); - } - assert resolvedMethod.isNonPrivateVirtualMethod(); - // First add the target for receiver type method.type. - Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(resolvedMethod); - // Add all matching targets from the subclass hierarchy. - DexMethod method = resolvedMethod.method; - // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined - // receiver type if available. - for (DexProgramClass programClass : - liveSubTypeInfo.getLiveSubTypes(method.holder).getProgramClasses()) { - if (!programClass.isInterface()) { - ResolutionResult methods = appView.appInfo().resolveMethodOnClass(programClass, method); - DexEncodedMethod target = methods.getSingleTarget(); - if (target != null && target.isNonPrivateVirtualMethod()) { - result.add(target); - } - } - } - return LookupResult.createResult(result, LookupResultCollectionState.Complete); - } - - // TODO(b/140204899): Leverage refined receiver type if available. - private LookupResult lookupInterfaceTargets( - AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) { - assert initialResolutionHolder.isInterface(); - if (resolvedMethod.isPrivateMethod()) { - // If the resolved reference is private there is no dispatch. - // This is assuming that the method is accessible, which implies self/nest access. - assert resolvedMethod.hasCode(); + // Only include if the target has code or is native. return LookupResult.createResult( Collections.singleton(resolvedMethod), LookupResultCollectionState.Complete); } assert resolvedMethod.isNonPrivateVirtualMethod(); Set<DexEncodedMethod> result = Sets.newIdentityHashSet(); - // Add default interface methods to the list of targets. - // - // This helps to make sure we take into account synthesized lambda classes - // that we are not aware of. Like in the following example, we know that all - // classes, XX in this case, override B::bar(), but there are also synthesized - // classes for lambda which don't, so we still need default method to be live. - // - // public static void main(String[] args) { - // X x = () -> {}; - // x.bar(); - // } - // - // interface X { - // void foo(); - // default void bar() { } - // } - // - // class XX implements X { - // public void foo() { } - // public void bar() { } - // } - // - addIfDefaultMethodWithLambdaInstantiations(liveSubTypeInfo, resolvedMethod, result); - - DexMethod method = resolvedMethod.method; - Consumer<DexEncodedMethod> addIfNotAbstract = - m -> { - if (!m.accessFlags.isAbstract()) { - result.add(m); + instantiatedInfo.forEachInstantiatedSubType( + resolvedHolder.type, + subClass -> { + DexClassAndMethod dexClassAndMethod = lookupVirtualDispatchTarget(subClass, appView); + if (dexClassAndMethod != null) { + addVirtualDispatchTarget( + dexClassAndMethod.getMethod(), resolvedHolder.isInterface(), result); } - }; - // Default methods are looked up when looking at a specific subtype that does not override - // them. Otherwise, we would look up default methods that are actually never used. However, we - // have to add bridge methods, otherwise we can remove a bridge that will be used. - Consumer<DexEncodedMethod> addIfNotAbstractAndBridge = - m -> { - if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) { - result.add(m); - } - }; - - // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined - // receiver type if available. - for (DexProgramClass clazz : - liveSubTypeInfo.getLiveSubTypes(method.holder).getProgramClasses()) { - if (clazz.isInterface()) { - ResolutionResult targetMethods = - appView.appInfo().resolveMethodOnInterface(clazz, method); - if (targetMethods.isSingleResolution()) { - // Sub-interfaces can have default implementations that override the resolved method. - // Therefore we have to add default methods in sub interfaces. - DexEncodedMethod singleTarget = targetMethods.getSingleTarget(); - addIfDefaultMethodWithLambdaInstantiations(liveSubTypeInfo, singleTarget, result); - addIfNotAbstractAndBridge.accept(singleTarget); - } - } else { - ResolutionResult targetMethods = appView.appInfo().resolveMethodOnClass(clazz, method); - if (targetMethods.isSingleResolution()) { - addIfNotAbstract.accept(targetMethods.getSingleTarget()); - } - } - } + }, + dexCallSite -> { + // TODO(b/148769279): We need to look at the call site to see if it overrides + // the resolved method or not. + }); return LookupResult.createResult(result, LookupResultCollectionState.Complete); } - private void addIfDefaultMethodWithLambdaInstantiations( - LiveSubTypeInfo liveSubTypesInfo, DexEncodedMethod method, Set<DexEncodedMethod> result) { - if (method == null) { - return; + private static void addVirtualDispatchTarget( + DexEncodedMethod target, boolean holderIsInterface, Set<DexEncodedMethod> result) { + assert !target.isPrivateMethod(); + if (holderIsInterface) { + // Add default interface methods to the list of targets. + // + // This helps to make sure we take into account synthesized lambda classes + // that we are not aware of. Like in the following example, we know that all + // classes, XX in this case, override B::bar(), but there are also synthesized + // classes for lambda which don't, so we still need default method to be live. + // + // public static void main(String[] args) { + // X x = () -> {}; + // x.bar(); + // } + // + // interface X { + // void foo(); + // default void bar() { } + // } + // + // class XX implements X { + // public void foo() { } + // public void bar() { } + // } + // + if (target.isDefaultMethod()) { + result.add(target); + } + // Default methods are looked up when looking at a specific subtype that does not override + // them. Otherwise, we would look up default methods that are actually never used. + // However, we have to add bridge methods, otherwise we can remove a bridge that will be + // used. + if (!target.accessFlags.isAbstract() && target.accessFlags.isBridge()) { + result.add(target); + } + } else { + result.add(target); } - if (method.hasCode()) { - LiveSubTypeResult liveSubTypes = liveSubTypesInfo.getLiveSubTypes(method.method.holder); - // TODO(b/148769279): The below is basically if (true), but should be changed when we - // have live sub type information. - if (liveSubTypes.getCallSites() == null || liveSubTypes.getCallSites() != null) { - result.add(method); + } + + /** + * This implements the logic for the actual method selection for a virtual target, according to + * https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokevirtual where + * we have an object ref on the stack. + */ + @Override + public DexClassAndMethod lookupVirtualDispatchTarget( + DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) { + // TODO(b/148591377): Enable this assertion. + // The dynamic type cannot be an interface. + // assert !dynamicInstance.isInterface(); + boolean allowPackageBlocked = resolvedMethod.accessFlags.isPackagePrivate(); + DexClass current = dynamicInstance; + DexEncodedMethod overrideTarget = resolvedMethod; + while (current != null) { + DexEncodedMethod candidate = lookupOverrideCandidate(overrideTarget, current); + if (candidate == DexEncodedMethod.SENTINEL && allowPackageBlocked) { + overrideTarget = findWideningOverride(resolvedMethod, current, appView); + allowPackageBlocked = false; + continue; + } + if (candidate == null || candidate == DexEncodedMethod.SENTINEL) { + // We cannot find a target above the resolved method. + if (current.type == overrideTarget.method.holder) { + return null; + } + current = current.superType == null ? null : appView.definitionFor(current.superType); + continue; + } + return DexClassAndMethod.create(current, candidate); + } + // TODO(b/149557233): Enable assertion. + // assert resolvedHolder.isInterface(); + DexEncodedMethod maximalSpecific = + lookupMaximallySpecificDispatchTarget(dynamicInstance, appView); + return maximalSpecific == null + ? null + : DexClassAndMethod.create( + appView.definitionFor(maximalSpecific.method.holder), maximalSpecific); + } + + private DexEncodedMethod lookupMaximallySpecificDispatchTarget( + DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) { + ResolutionResult maximallySpecificResult = + appView.appInfo().resolveMaximallySpecificMethods(dynamicInstance, resolvedMethod.method); + if (maximallySpecificResult.isSingleResolution()) { + return maximallySpecificResult.asSingleResolution().resolvedMethod; + } + return null; + } + + /** + * C contains a declaration for an instance method m that overrides (§5.4.5) the resolved + * method, then m is the method to be invoked. If the candidate is not a valid override, we + * return sentinel to indicate that we have to search for a method that is widening access + * inside the package. + */ + private static DexEncodedMethod lookupOverrideCandidate( + DexEncodedMethod method, DexClass clazz) { + DexEncodedMethod candidate = clazz.lookupVirtualMethod(method.method); + assert candidate == null || !candidate.isPrivateMethod(); + if (candidate != null) { + return isOverriding(method, candidate) ? candidate : DexEncodedMethod.SENTINEL; + } + return null; + } + + private static DexEncodedMethod findWideningOverride( + DexEncodedMethod resolvedMethod, + DexClass clazz, + AppView<? extends AppInfoWithClassHierarchy> appView) { + // Otherwise, lookup to first override that is distinct from resolvedMethod. + assert resolvedMethod.accessFlags.isPackagePrivate(); + while (clazz.superType != null) { + clazz = appView.definitionFor(clazz.superType); + if (clazz == null) { + return resolvedMethod; + } + DexEncodedMethod otherOverride = clazz.lookupVirtualMethod(resolvedMethod.method); + if (otherOverride != null + && isOverriding(resolvedMethod, otherOverride) + && (otherOverride.accessFlags.isPublic() || otherOverride.accessFlags.isProtected())) { + assert resolvedMethod != otherOverride; + return otherOverride; } } + return resolvedMethod; + } + + /** + * Implementation of method overriding according to the jvm specification + * https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.5 + * + * <p>The implementation assumes that the holder of the candidate is a subtype of the holder of + * the resolved method. It also assumes that resolvedMethod is the actual method to find a + * lookup for (that is, it is either mA or m'). + */ + private static boolean isOverriding( + DexEncodedMethod resolvedMethod, DexEncodedMethod candidate) { + assert resolvedMethod.method.match(candidate.method); + assert !candidate.isPrivateMethod(); + if (resolvedMethod.accessFlags.isPublic() || resolvedMethod.accessFlags.isProtected()) { + return true; + } + // For package private methods, a valid override has to be inside the package. + assert resolvedMethod.accessFlags.isPackagePrivate(); + return resolvedMethod.method.holder.isSamePackage(candidate.method.holder); } } @@ -488,9 +548,17 @@ @Override public LookupResult lookupVirtualDispatchTargets( - AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) { + DexProgramClass context, + AppView<? extends AppInfoWithClassHierarchy> appView, + InstantiatedSubTypeInfo instantiatedInfo) { return LookupResult.getIncompleteEmptyResult(); } + + @Override + public DexClassAndMethod lookupVirtualDispatchTarget( + DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) { + return null; + } } /** Singleton result for the special case resolving the array clone() method. */
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java index 5c22878..3beb9ba 100644 --- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java +++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -8,11 +8,9 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.IteratorUtils; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; +import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; +import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator; import java.util.function.Consumer; public class RewrittenPrototypeDescription { @@ -21,15 +19,9 @@ public static class Builder { - private int argumentIndex = -1; private boolean isAlwaysNull = false; private DexType type = null; - public Builder setArgumentIndex(int argumentIndex) { - this.argumentIndex = argumentIndex; - return this; - } - public Builder setIsAlwaysNull() { this.isAlwaysNull = true; return this; @@ -41,18 +33,15 @@ } public RemovedArgumentInfo build() { - assert argumentIndex >= 0; assert type != null; - return new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type); + return new RemovedArgumentInfo(isAlwaysNull, type); } } - private final int argumentIndex; private final boolean isAlwaysNull; private final DexType type; - private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull, DexType type) { - this.argumentIndex = argumentIndex; + private RemovedArgumentInfo(boolean isAlwaysNull, DexType type) { this.isAlwaysNull = isAlwaysNull; this.type = type; } @@ -61,10 +50,6 @@ return new Builder(); } - public int getArgumentIndex() { - return argumentIndex; - } - public DexType getType() { return type; } @@ -76,61 +61,40 @@ public boolean isNeverUsed() { return !isAlwaysNull; } - - RemovedArgumentInfo withArgumentIndex(int argumentIndex) { - return this.argumentIndex != argumentIndex - ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type) - : this; - } } - public static class RemovedArgumentsInfo { + public static class RemovedArgumentInfoCollection { - private static final RemovedArgumentsInfo empty = new RemovedArgumentsInfo(null); + private static final RemovedArgumentInfoCollection EMPTY = new RemovedArgumentInfoCollection(); - private final List<RemovedArgumentInfo> removedArguments; + private final Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments; - public RemovedArgumentsInfo(List<RemovedArgumentInfo> removedArguments) { - assert verifyRemovedArguments(removedArguments); + // Specific constructor for empty. + private RemovedArgumentInfoCollection() { + this.removedArguments = new Int2ReferenceLinkedOpenHashMap<>(); + } + + private RemovedArgumentInfoCollection( + Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments) { + assert removedArguments != null : "should use empty."; + assert !removedArguments.isEmpty() : "should use empty."; this.removedArguments = removedArguments; } - private static boolean verifyRemovedArguments(List<RemovedArgumentInfo> removedArguments) { - if (removedArguments != null && !removedArguments.isEmpty()) { - // Check that list is sorted by argument indices. - int lastArgumentIndex = removedArguments.get(0).getArgumentIndex(); - for (int i = 1; i < removedArguments.size(); ++i) { - int currentArgumentIndex = removedArguments.get(i).getArgumentIndex(); - assert lastArgumentIndex < currentArgumentIndex; - lastArgumentIndex = currentArgumentIndex; - } - } - return true; + public static RemovedArgumentInfoCollection empty() { + return EMPTY; } - public static RemovedArgumentsInfo empty() { - return empty; - } - - public ListIterator<RemovedArgumentInfo> iterator() { - return removedArguments == null - ? Collections.emptyListIterator() - : removedArguments.listIterator(); + public RemovedArgumentInfo getArgumentInfo(int argIndex) { + return removedArguments.get(argIndex); } public boolean hasRemovedArguments() { - return removedArguments != null && !removedArguments.isEmpty(); + return !removedArguments.isEmpty(); } public boolean isArgumentRemoved(int argumentIndex) { - if (removedArguments != null) { - for (RemovedArgumentInfo info : removedArguments) { - if (info.getArgumentIndex() == argumentIndex) { - return true; - } - } - } - return false; + return removedArguments.containsKey(argumentIndex); } public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) { @@ -156,8 +120,7 @@ return removedArguments != null ? removedArguments.size() : 0; } - public RemovedArgumentsInfo combine(RemovedArgumentsInfo info) { - assert info != null; + public RemovedArgumentInfoCollection combine(RemovedArgumentInfoCollection info) { if (hasRemovedArguments()) { if (!info.hasRemovedArguments()) { return this; @@ -166,19 +129,32 @@ return info; } - List<RemovedArgumentInfo> newRemovedArguments = new LinkedList<>(removedArguments); - ListIterator<RemovedArgumentInfo> iterator = newRemovedArguments.listIterator(); + Int2ReferenceSortedMap<RemovedArgumentInfo> newRemovedArguments = + new Int2ReferenceLinkedOpenHashMap<>(); + newRemovedArguments.putAll(removedArguments); + IntBidirectionalIterator iterator = removedArguments.keySet().iterator(); int offset = 0; - for (RemovedArgumentInfo pending : info.removedArguments) { - RemovedArgumentInfo next = IteratorUtils.peekNext(iterator); - while (next != null && next.getArgumentIndex() <= pending.getArgumentIndex() + offset) { - iterator.next(); - next = IteratorUtils.peekNext(iterator); + for (int pendingArgIndex : info.removedArguments.keySet()) { + int nextArgindex = peekNextOrMax(iterator); + while (nextArgindex <= pendingArgIndex + offset) { + iterator.nextInt(); + nextArgindex = peekNextOrMax(iterator); offset++; } - iterator.add(pending.withArgumentIndex(pending.getArgumentIndex() + offset)); + assert !newRemovedArguments.containsKey(pendingArgIndex + offset); + newRemovedArguments.put( + pendingArgIndex + offset, info.removedArguments.get(pendingArgIndex)); } - return new RemovedArgumentsInfo(newRemovedArguments); + return new RemovedArgumentInfoCollection(newRemovedArguments); + } + + static int peekNextOrMax(IntBidirectionalIterator iterator) { + if (iterator.hasNext()) { + int i = iterator.nextInt(); + iterator.previousInt(); + return i; + } + return Integer.MAX_VALUE; } public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover( @@ -192,22 +168,46 @@ } return null; } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments; + + public Builder addRemovedArgument(int argIndex, RemovedArgumentInfo argInfo) { + if (removedArguments == null) { + removedArguments = new Int2ReferenceLinkedOpenHashMap<>(); + } + assert !removedArguments.containsKey(argIndex); + removedArguments.put(argIndex, argInfo); + return this; + } + + public RemovedArgumentInfoCollection build() { + if (removedArguments == null || removedArguments.isEmpty()) { + return EMPTY; + } + return new RemovedArgumentInfoCollection(removedArguments); + } + } } private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription(); private final boolean hasBeenChangedToReturnVoid; private final boolean extraNullParameter; - private final RemovedArgumentsInfo removedArgumentsInfo; + private final RemovedArgumentInfoCollection removedArgumentsInfo; private RewrittenPrototypeDescription() { - this(false, false, RemovedArgumentsInfo.empty()); + this(false, false, RemovedArgumentInfoCollection.empty()); } private RewrittenPrototypeDescription( boolean hasBeenChangedToReturnVoid, boolean extraNullParameter, - RemovedArgumentsInfo removedArgumentsInfo) { + RemovedArgumentInfoCollection removedArgumentsInfo) { assert removedArgumentsInfo != null; this.extraNullParameter = extraNullParameter; this.hasBeenChangedToReturnVoid = hasBeenChangedToReturnVoid; @@ -215,7 +215,7 @@ } public static RewrittenPrototypeDescription createForUninstantiatedTypes( - boolean hasBeenChangedToReturnVoid, RemovedArgumentsInfo removedArgumentsInfo) { + boolean hasBeenChangedToReturnVoid, RemovedArgumentInfoCollection removedArgumentsInfo) { return new RewrittenPrototypeDescription( hasBeenChangedToReturnVoid, false, removedArgumentsInfo); } @@ -227,7 +227,7 @@ public boolean isEmpty() { return !extraNullParameter && !hasBeenChangedToReturnVoid - && !getRemovedArgumentsInfo().hasRemovedArguments(); + && !getRemovedArgumentInfoCollection().hasRemovedArguments(); } public boolean hasExtraNullParameter() { @@ -238,7 +238,7 @@ return hasBeenChangedToReturnVoid; } - public RemovedArgumentsInfo getRemovedArgumentsInfo() { + public RemovedArgumentInfoCollection getRemovedArgumentInfoCollection() { return removedArgumentsInfo; } @@ -276,7 +276,7 @@ : this; } - public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentsInfo other) { + public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentInfoCollection other) { return new RewrittenPrototypeDescription( hasBeenChangedToReturnVoid, extraNullParameter, removedArgumentsInfo.combine(other)); }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java index b463c75..1e8bb52 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -4,11 +4,15 @@ package com.android.tools.r8.ir.analysis.fieldaccess; +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.NewInstance; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; @@ -56,20 +60,33 @@ public void recordFieldAccesses( IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) { - if (!code.metadata().mayHaveFieldInstruction() || !methodProcessor.isPrimary()) { + if (!methodProcessor.isPrimary()) { return; } - Iterable<FieldInstruction> fieldInstructions = - code.instructions(Instruction::isFieldInstruction); - for (FieldInstruction fieldInstruction : fieldInstructions) { - DexEncodedField encodedField = appView.appInfo().resolveField(fieldInstruction.getField()); - if (encodedField != null && encodedField.isProgramField(appView)) { - if (fieldAssignmentTracker != null) { - fieldAssignmentTracker.recordFieldAccess(fieldInstruction, encodedField, code.method); + if (!code.metadata().mayHaveFieldInstruction() && !code.metadata().mayHaveNewInstance()) { + return; + } + + for (Instruction instruction : code.instructions()) { + if (instruction.isFieldInstruction()) { + FieldInstruction fieldInstruction = instruction.asFieldInstruction(); + DexEncodedField encodedField = appView.appInfo().resolveField(fieldInstruction.getField()); + if (encodedField != null && encodedField.isProgramField(appView)) { + if (fieldAssignmentTracker != null) { + fieldAssignmentTracker.recordFieldAccess(fieldInstruction, encodedField, code.method); + } + if (fieldBitAccessAnalysis != null) { + fieldBitAccessAnalysis.recordFieldAccess(fieldInstruction, encodedField, feedback); + } } - if (fieldBitAccessAnalysis != null) { - fieldBitAccessAnalysis.recordFieldAccess(fieldInstruction, encodedField, feedback); + } else if (instruction.isNewInstance()) { + NewInstance newInstance = instruction.asNewInstance(); + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(newInstance.clazz)); + if (clazz != null) { + if (fieldAssignmentTracker != null) { + fieldAssignmentTracker.recordAllocationSite(newInstance, clazz, code.method); + } } } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java index 0b0b542..0bef6b1 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -7,13 +7,22 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.FieldAccessInfoCollection; +import com.android.tools.r8.graph.ObjectAllocationInfoCollection; import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.ir.analysis.value.BottomValue; +import com.android.tools.r8.ir.analysis.value.UnknownValue; import com.android.tools.r8.ir.code.FieldInstruction; +import com.android.tools.r8.ir.code.InvokeDirect; +import com.android.tools.r8.ir.code.NewInstance; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult; import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo; -import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.objects.Reference2IntMap; @@ -21,9 +30,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; public class FieldAssignmentTracker { @@ -35,12 +46,52 @@ // processed when a field no longer has any incoming edges. private final FieldAccessGraph fieldAccessGraph; + // An object allocation graph with edges from methods to the classes they instantiate. Edges are + // removed from the graph as we process methods, such that we can conclude that all allocation + // sites have been seen when a class no longer has any incoming edges. + private final ObjectAllocationGraph objectAllocationGraph; + // The set of fields that may store a non-zero value. private final Set<DexEncodedField> nonZeroFields = Sets.newConcurrentHashSet(); + private final Map<DexProgramClass, Map<DexEncodedField, AbstractValue>> + abstractInstanceFieldValues = new ConcurrentHashMap<>(); + FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) { this.appView = appView; this.fieldAccessGraph = new FieldAccessGraph(appView); + this.objectAllocationGraph = new ObjectAllocationGraph(appView); + initializeAbstractInstanceFieldValues(); + } + + /** + * For each class with known allocation sites, adds a mapping from clazz -> instance field -> + * bottom. + * + * <p>If an entry (clazz, instance field) is missing in {@link #abstractInstanceFieldValues}, it + * is interpreted as if we known nothing about the value of the field. + */ + private void initializeAbstractInstanceFieldValues() { + ObjectAllocationInfoCollection objectAllocationInfos = + appView.appInfo().getObjectAllocationInfoCollection(); + objectAllocationInfos.forEachClassWithKnownAllocationSites( + (clazz, allocationSites) -> { + if (appView.appInfo().isInstantiatedIndirectly(clazz)) { + // TODO(b/147652121): Handle classes that are instantiated indirectly. + return; + } + List<DexEncodedField> instanceFields = clazz.instanceFields(); + if (instanceFields.isEmpty()) { + // No instance fields to track. + return; + } + Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass = + new IdentityHashMap<>(); + for (DexEncodedField field : clazz.instanceFields()) { + abstractInstanceFieldValuesForClass.put(field, BottomValue.getInstance()); + } + abstractInstanceFieldValues.put(clazz, abstractInstanceFieldValuesForClass); + }); } private boolean isAlwaysZero(DexEncodedField field) { @@ -72,16 +123,98 @@ } } - private void recordAllFieldPutsProcessed(DexEncodedField field, OptimizationFeedback feedback) { + void recordAllocationSite( + NewInstance instruction, DexProgramClass clazz, DexEncodedMethod context) { + Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass = + abstractInstanceFieldValues.get(clazz); + if (abstractInstanceFieldValuesForClass == null) { + // We are not tracking the value of any of clazz' instance fields. + return; + } + + InvokeDirect invoke = instruction.getUniqueConstructorInvoke(appView.dexItemFactory()); + if (invoke == null) { + // We just lost track. + abstractInstanceFieldValues.remove(clazz); + return; + } + + DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context.method.holder); + if (singleTarget == null) { + // We just lost track. + abstractInstanceFieldValues.remove(clazz); + return; + } + + InstanceFieldInitializationInfoCollection initializationInfoCollection = + singleTarget.getOptimizationInfo().getInstanceInitializerInfo().fieldInitializationInfos(); + + // Synchronize on the lattice element (abstractInstanceFieldValuesForClass) in case we process + // another allocation site of `clazz` concurrently. + synchronized (abstractInstanceFieldValuesForClass) { + Iterator<Map.Entry<DexEncodedField, AbstractValue>> iterator = + abstractInstanceFieldValuesForClass.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry<DexEncodedField, AbstractValue> entry = iterator.next(); + DexEncodedField field = entry.getKey(); + InstanceFieldInitializationInfo initializationInfo = + initializationInfoCollection.get(field); + if (initializationInfo.isArgumentInitializationInfo()) { + InstanceFieldArgumentInitializationInfo argumentInitializationInfo = + initializationInfo.asArgumentInitializationInfo(); + Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex()); + AbstractValue abstractValue = + argument.getAbstractValue(appView, context.method.holder).join(entry.getValue()); + assert !abstractValue.isBottom(); + if (!abstractValue.isUnknown()) { + entry.setValue(abstractValue); + continue; + } + } else { + assert initializationInfo.isUnknown(); + } + + // We just lost track for this field. + iterator.remove(); + } + } + } + + private void recordAllFieldPutsProcessed( + DexEncodedField field, OptimizationFeedbackDelayed feedback) { if (isAlwaysZero(field)) { feedback.recordFieldHasAbstractValue( field, appView, appView.abstractValueFactory().createSingleNumberValue(0)); } } - public void waveDone(Collection<DexEncodedMethod> wave, OptimizationFeedback feedback) { + private void recordAllAllocationsSitesProcessed( + DexProgramClass clazz, OptimizationFeedbackDelayed feedback) { + Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass = + abstractInstanceFieldValues.get(clazz); + if (abstractInstanceFieldValuesForClass == null) { + return; + } + + for (DexEncodedField field : clazz.instanceFields()) { + AbstractValue abstractValue = + abstractInstanceFieldValuesForClass.getOrDefault(field, UnknownValue.getInstance()); + if (abstractValue.isBottom()) { + feedback.modifyAppInfoWithLiveness(modifier -> modifier.removeInstantiatedType(clazz)); + break; + } + if (abstractValue.isUnknown()) { + continue; + } + feedback.recordFieldHasAbstractValue(field, appView, abstractValue); + } + } + + public void waveDone(Collection<DexEncodedMethod> wave, OptimizationFeedbackDelayed feedback) { for (DexEncodedMethod method : wave) { fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback)); + objectAllocationGraph.markProcessed( + method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback)); } } @@ -139,4 +272,42 @@ } } } + + static class ObjectAllocationGraph { + + // The classes instantiated by each method. + private final Map<DexEncodedMethod, List<DexProgramClass>> objectAllocations = + new IdentityHashMap<>(); + + // The number of allocation sites that have not yet been processed per class. + private final Reference2IntMap<DexProgramClass> pendingObjectAllocations = + new Reference2IntOpenHashMap<>(); + + ObjectAllocationGraph(AppView<AppInfoWithLiveness> appView) { + ObjectAllocationInfoCollection objectAllocationInfos = + appView.appInfo().getObjectAllocationInfoCollection(); + objectAllocationInfos.forEachClassWithKnownAllocationSites( + (clazz, contexts) -> { + for (DexEncodedMethod context : contexts) { + objectAllocations.computeIfAbsent(context, ignore -> new ArrayList<>()).add(clazz); + } + pendingObjectAllocations.put(clazz, contexts.size()); + }); + } + + void markProcessed( + DexEncodedMethod method, Consumer<DexProgramClass> allAllocationsSitesSeenConsumer) { + List<DexProgramClass> allocationSitesInMethod = objectAllocations.get(method); + if (allocationSitesInMethod != null) { + for (DexProgramClass type : allocationSitesInMethod) { + int numberOfPendingAllocationSites = pendingObjectAllocations.removeInt(type) - 1; + if (numberOfPendingAllocationSites > 0) { + pendingObjectAllocations.put(type, numberOfPendingAllocationSites); + } else { + allAllocationsSitesSeenConsumer.accept(type); + } + } + } + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java index 29dc41b..2289a28 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -275,7 +275,7 @@ return true; } - private void updateFieldOptimizationInfo(DexEncodedField field, Value value) { + void updateFieldOptimizationInfo(DexEncodedField field, Value value) { // Abstract value. Value root = value.getAliasedValue(); AbstractValue abstractValue = computeAbstractValue(root);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java index d636715..ecc93b7 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -5,15 +5,29 @@ package com.android.tools.r8.ir.analysis.fieldvalueanalysis; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.ir.code.Argument; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory; import com.android.tools.r8.shaking.AppInfoWithLiveness; public class InstanceFieldValueAnalysis extends FieldValueAnalysis { + // Information about how this instance constructor initializes the fields on the newly created + // instance. + private final InstanceFieldInitializationInfoCollection.Builder builder = + InstanceFieldInitializationInfoCollection.builder(); + + private final InstanceFieldInitializationInfoFactory factory; + private InstanceFieldValueAnalysis( AppView<AppInfoWithLiveness> appView, IRCode code, @@ -21,9 +35,14 @@ DexProgramClass clazz, DexEncodedMethod method) { super(appView, code, feedback, clazz, method); + factory = appView.instanceFieldInitializationInfoFactory(); } - public static void run( + /** + * Returns information about how this instance constructor initializes the fields on the newly + * created instance. + */ + public static InstanceFieldInitializationInfoCollection run( AppView<?> appView, IRCode code, ClassInitializerDefaultsResult classInitializerDefaultsResult, @@ -34,16 +53,32 @@ assert method.isInstanceInitializer(); DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass(); if (!appView.options().enableValuePropagationForInstanceFields) { - return; + return EmptyInstanceFieldInitializationInfoCollection.getInstance(); } DexEncodedMethod otherInstanceInitializer = clazz.lookupDirectMethod(other -> other.isInstanceInitializer() && other != method); if (otherInstanceInitializer != null) { // Conservatively bail out. // TODO(b/125282093): Handle multiple instance initializers on the same class. - return; + return EmptyInstanceFieldInitializationInfoCollection.getInstance(); } - new InstanceFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method) - .computeFieldOptimizationInfo(classInitializerDefaultsResult); + InstanceFieldValueAnalysis analysis = + new InstanceFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method); + analysis.computeFieldOptimizationInfo(classInitializerDefaultsResult); + return analysis.builder.build(); + } + + @Override + void updateFieldOptimizationInfo(DexEncodedField field, Value value) { + super.updateFieldOptimizationInfo(field, value); + + // If this instance field is initialized with an argument, then record this in the instance + // field initialization info. + Value root = value.getAliasedValue(); + if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) { + Argument argument = root.definition.asArgument(); + builder.recordInitializationInfo( + field, factory.createArgumentInitializationInfo(argument.getIndex())); + } } }
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 37a8dc6..717df30 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
@@ -21,9 +21,9 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.CallGraph.Node; import com.android.tools.r8.ir.conversion.MethodProcessor; -import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.Inliner.Reason; +import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy; import com.android.tools.r8.utils.PredicateSet; @@ -91,7 +91,7 @@ public void inlineCallsToDynamicMethod( DexEncodedMethod method, IRCode code, - CodeRewriter codeRewriter, + EnumValueOptimizer enumValueOptimizer, OptimizationFeedback feedback, MethodProcessor methodProcessor, Inliner inliner) { @@ -103,8 +103,8 @@ // Run the enum optimization to optimize all Enum.ordinal() invocations. This is required to // get rid of the enum switch in dynamicMethod(). - if (appView.options().enableEnumValueOptimization) { - codeRewriter.rewriteConstantEnumMethodCalls(code); + if (enumValueOptimizer != null) { + enumValueOptimizer.rewriteConstantEnumMethodCalls(code); } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java index 007fd9f..5371255 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -8,6 +8,10 @@ public abstract boolean isNonTrivial(); + public boolean isBottom() { + return false; + } + public boolean isZero() { return false; } @@ -69,7 +73,13 @@ } public AbstractValue join(AbstractValue other) { - if (this.equals(other)) { + if (isBottom() || other.isUnknown()) { + return other; + } + if (isUnknown() || other.isBottom()) { + return this; + } + if (equals(other)) { return this; } return UnknownValue.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java new file mode 100644 index 0000000..ca376e7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
@@ -0,0 +1,41 @@ +// 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.analysis.value; + +public class BottomValue extends AbstractValue { + + private static final BottomValue INSTANCE = new BottomValue(); + + private BottomValue() {} + + public static BottomValue getInstance() { + return INSTANCE; + } + + @Override + public boolean isBottom() { + return true; + } + + @Override + public boolean isNonTrivial() { + return true; + } + + @Override + public boolean equals(Object o) { + return this == o; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "BottomValue"; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java index 93c39ec..1db2ba6 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Argument.java +++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -21,12 +21,33 @@ */ public class Argument extends Instruction { + private final int index; private final boolean knownToBeBoolean; - public Argument(Value outValue, boolean knownToBeBoolean) { + public Argument(Value outValue, int index, boolean knownToBeBoolean) { super(outValue); + this.index = index; this.knownToBeBoolean = knownToBeBoolean; - outValue.markAsArgument(); + } + + public int getIndex() { + assert verifyIndex(); + return index; + } + + private boolean verifyIndex() { + int index = 0; + InstructionIterator instructionIterator = getBlock().iterator(); + while (instructionIterator.hasNext()) { + Instruction instruction = instructionIterator.next(); + assert instruction.isArgument(); + if (instruction == this) { + assert index == this.index; + return true; + } + index++; + } + return false; } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java index f4bb790..584ea2c 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
@@ -621,6 +621,10 @@ return instructions.isEmpty(); } + public int size() { + return instructions.size(); + } + public boolean isReturnBlock() { return exit().isReturn(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java index 39b6fb8..c8d6abc 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -21,6 +21,7 @@ import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.analysis.value.UnknownValue; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.Sets; import java.util.Collections; import java.util.List; @@ -128,7 +129,8 @@ if (field.holder.classInitializationMayHaveSideEffects( appView, // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context, type).isTrue())) { + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet())) { return AbstractError.top(); } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java index f7411d9..b074e44 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java +++ b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; +import java.util.function.Predicate; // Value that has a fixed register allocated. These are used for inserting spill, restore, and phi // moves in the spilling register allocator. @@ -50,6 +51,11 @@ } @Override + public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) { + return false; + } + + @Override public boolean isFixedRegisterValue() { return true; }
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 39c3d39..e620cb1 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
@@ -826,6 +826,9 @@ } else if (instruction.isMul()) { assert metadata.mayHaveMul() && metadata.mayHaveArithmeticOrLogicalBinop() : "IR metadata should indicate that code has a mul"; + } else if (instruction.isNewInstance()) { + assert metadata.mayHaveNewInstance() + : "IR metadata should indicate that code has a new-instance"; } else if (instruction.isRem()) { assert metadata.mayHaveRem() && metadata.mayHaveArithmeticOrLogicalBinop() : "IR metadata should indicate that code has a rem";
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 c4d55d8..8db09e9 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
@@ -206,6 +206,10 @@ return get(Opcodes.MUL); } + public boolean mayHaveNewInstance() { + return get(Opcodes.NEW_INSTANCE); + } + public boolean mayHaveOr() { return get(Opcodes.OR); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java index bdba162..df0c10d 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -88,7 +88,8 @@ appView .appInfo() .resolveMethod(method.holder, method) - .lookupVirtualDispatchTargets(appView.withLiveness()) + .lookupVirtualDispatchTargets( + appView.definitionForProgramType(invocationContext), appView.withLiveness()) .asLookupResultSuccess(); if (lookupResult == null) { return null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java index 6ba4a3a..3a7969a 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -24,6 +24,7 @@ import com.android.tools.r8.ir.optimize.InliningConstraints; import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.Sets; import java.util.List; import java.util.function.Predicate; @@ -197,7 +198,8 @@ appView, // Types that are a super type of `context` are guaranteed to be initialized // already. - type -> appView.isSubtype(context, type).isTrue()); + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet()); } return targetMayHaveSideEffects;
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java index aed4b9e..cd88da6 100644 --- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java +++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -24,6 +24,7 @@ import com.android.tools.r8.ir.conversion.DexBuilder; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; +import com.google.common.collect.Sets; public class NewInstance extends Instruction { @@ -188,7 +189,8 @@ if (definition.classInitializationMayHaveSideEffects( appView, // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context, type).isTrue())) { + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet())) { return true; } @@ -228,7 +230,8 @@ return clazz.classInitializationMayHaveSideEffects( appView, // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context, type).isTrue()); + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet()); } else { // In D8, this instruction may trigger class initialization if the holder of the field is // different from the current context.
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 eecf8c3..95c2608 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
@@ -30,6 +30,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.function.Predicate; public class Phi extends Value implements InstructionOrPhi { @@ -62,6 +63,11 @@ } @Override + public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) { + return false; + } + + @Override public boolean isPhi() { return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java index f59eb4b..08285c5 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -27,6 +27,7 @@ import com.android.tools.r8.ir.conversion.DexBuilder; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; +import com.google.common.collect.Sets; import java.util.Set; public class StaticGet extends FieldInstruction { @@ -236,7 +237,8 @@ return holder.classInitializationMayHaveSideEffects( appView, // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context, type).isTrue()); + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet()); } else { // In D8, this instruction may trigger class initialization if the holder of the field is // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java index 63e5c10..c9bab8b 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -28,6 +28,7 @@ import com.android.tools.r8.ir.regalloc.RegisterAllocator; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.ProguardMemberRule; +import com.google.common.collect.Sets; public class StaticPut extends FieldInstruction { @@ -235,7 +236,8 @@ return holder.classInitializationMayHaveSideEffects( appView, // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context, type).isTrue()); + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet()); } else { // In D8, this instruction may trigger class initialization if the holder of the field is // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java index 8e75d62..5566552 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Value.java +++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -226,7 +226,6 @@ private LiveIntervals liveIntervals; private int needsRegister = -1; private boolean isThis = false; - private boolean isArgument = false; private LongInterval valueRange; private DebugData debugData; protected TypeLatticeElement typeLattice; @@ -893,6 +892,10 @@ return root.definition.getAbstractValue(appView, context); } + public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) { + return predicate.test(definition); + } + public boolean isPhi() { return false; } @@ -910,14 +913,8 @@ || typeLattice.nullability().isDefinitelyNotNull(); } - public void markAsArgument() { - assert !isArgument; - assert !isThis; - isArgument = true; - } - public boolean isArgument() { - return isArgument; + return isDefinedByInstructionSatisfying(Instruction::isArgument); } public boolean onlyDependsOnArgument() { @@ -941,21 +938,6 @@ } } - public int computeArgumentPosition(IRCode code) { - assert isArgument; - int position = 0; - InstructionIterator instructionIterator = code.entryBlock().iterator(); - while (instructionIterator.hasNext()) { - Instruction instruction = instructionIterator.next(); - assert instruction.isArgument(); - if (instruction.outValue() == this) { - return position; - } - position++; - } - throw new Unreachable(); - } - public boolean knownToBeBoolean() { return knownToBeBoolean(null); } @@ -987,7 +969,7 @@ } public void markAsThis() { - assert isArgument; + assert isArgument(); assert !isThis; isThis = true; }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java index 56462db..2bb3869 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -169,7 +169,7 @@ ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method); DexEncodedMethod target = resolutionResult.getSingleTarget(); if (target != null) { - processInvokeWithDynamicDispatch(type, target); + processInvokeWithDynamicDispatch(type, target, context.holder); } } else { DexEncodedMethod singleTarget = @@ -190,7 +190,7 @@ } private void processInvokeWithDynamicDispatch( - Invoke.Type type, DexEncodedMethod encodedTarget) { + Invoke.Type type, DexEncodedMethod encodedTarget, DexType context) { DexMethod target = encodedTarget.method; DexClass clazz = appView.definitionFor(target.holder); if (clazz == null) { @@ -214,7 +214,8 @@ appView.appInfo().resolveMethod(method.holder, method, isInterface); if (resolution.isVirtualTarget()) { LookupResult lookupResult = - resolution.lookupVirtualDispatchTargets(appView, appView.appInfo()); + resolution.lookupVirtualDispatchTargets( + appView.definitionForProgramType(context), appView); if (lookupResult.isLookupResultSuccess()) { return lookupResult.asLookupResultSuccess().getMethodTargets(); }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java index a7c90fe..8f6ea1e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -385,7 +385,7 @@ inPrelude = true; state.buildPrelude(canonicalPositions.getPreamblePosition()); setLocalVariableLists(); - DexSourceCode.buildArgumentsWithUnusedArgumentStubs(builder, 0, method, state::write); + builder.buildArgumentsWithUnusedArgumentStubs(0, method, state::write); // Add debug information for all locals at the initial label. Int2ReferenceMap<DebugLocalInfo> locals = getLocalVariables(0).locals; if (!locals.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java index 01dcf5d..2436ed1 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -40,10 +40,6 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo; -import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo; -import com.android.tools.r8.ir.analysis.type.Nullability; -import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; import com.android.tools.r8.ir.code.CanonicalPositions; import com.android.tools.r8.ir.code.CatchHandlers; import com.android.tools.r8.ir.code.Position; @@ -51,7 +47,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; @@ -142,8 +137,7 @@ if (code.incomingRegisterSize == 0) { return; } - buildArgumentsWithUnusedArgumentStubs( - builder, + builder.buildArgumentsWithUnusedArgumentStubs( code.registerSize - code.incomingRegisterSize, method, DexSourceCode::doNothingWriteConsumer); @@ -153,62 +147,6 @@ // Intentionally empty. } - public static void buildArgumentsWithUnusedArgumentStubs( - IRBuilder builder, - int register, - DexEncodedMethod method, - BiConsumer<Integer, DexType> writeCallback) { - RemovedArgumentsInfo removedArgumentsInfo = - builder.getPrototypeChanges().getRemovedArgumentsInfo(); - ListIterator<RemovedArgumentInfo> removedArgumentIterator = removedArgumentsInfo.iterator(); - RemovedArgumentInfo nextRemovedArgument = - removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null; - - // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument - // block. - int argumentIndex = 0; - - if (!method.isStatic()) { - writeCallback.accept(register, method.method.holder); - builder.addThisArgument(register); - ++argumentIndex; - ++register; - } - - int numberOfArguments = - method.method.proto.parameters.values.length - + removedArgumentsInfo.numberOfRemovedArguments() - + (method.isStatic() ? 0 : 1); - - for (int usedArgumentIndex = 0; argumentIndex < numberOfArguments; ++argumentIndex) { - TypeLatticeElement type; - if (nextRemovedArgument != null && nextRemovedArgument.getArgumentIndex() == argumentIndex) { - writeCallback.accept(register, nextRemovedArgument.getType()); - type = - TypeLatticeElement.fromDexType( - nextRemovedArgument.getType(), Nullability.maybeNull(), builder.appView); - builder.addConstantOrUnusedArgument(register, nextRemovedArgument); - nextRemovedArgument = - removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null; - } else { - DexType dexType = method.method.proto.parameters.values[usedArgumentIndex++]; - writeCallback.accept(register, dexType); - type = - TypeLatticeElement.fromDexType( - dexType, - Nullability.maybeNull(), - builder.appView); - if (dexType.isBooleanType()) { - builder.addBooleanNonThisArgument(register); - } else { - builder.addNonThisArgument(register, type); - } - } - register += type.requiredRegisters(); - } - builder.flushArgumentInstructions(); - } - @Override public void buildPostlude(IRBuilder builder) { // Intentionally left empty. (Needed in the Java bytecode frontend for synchronization support.)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java index a39a66b..78b1405 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -36,6 +36,7 @@ import com.android.tools.r8.graph.GraphLense; import com.android.tools.r8.graph.RewrittenPrototypeDescription; import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo; +import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection; import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; @@ -135,6 +136,7 @@ import java.util.Map; import java.util.Queue; import java.util.Set; +import java.util.function.BiConsumer; /** * Builder object for constructing high-level IR from dex bytecode. @@ -448,11 +450,12 @@ } else { this.prototypeChanges = appView.graphLense().lookupPrototypeChanges(method.method); - if (Log.ENABLED && prototypeChanges.getRemovedArgumentsInfo().hasRemovedArguments()) { + if (Log.ENABLED + && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) { Log.info( getClass(), "Removed " - + prototypeChanges.getRemovedArgumentsInfo().numberOfRemovedArguments() + + prototypeChanges.getRemovedArgumentInfoCollection().numberOfRemovedArguments() + " arguments from " + method.toSourceString()); } @@ -503,6 +506,53 @@ currentBlock = block; } + public void buildArgumentsWithUnusedArgumentStubs( + int register, DexEncodedMethod method, BiConsumer<Integer, DexType> writeCallback) { + RemovedArgumentInfoCollection removedArgumentsInfo = + prototypeChanges.getRemovedArgumentInfoCollection(); + + // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument + // block. + int argumentIndex = 0; + + if (!method.isStatic()) { + writeCallback.accept(register, method.method.holder); + addThisArgument(register); + argumentIndex++; + register++; + } + + int numberOfArguments = + method.method.proto.parameters.values.length + + removedArgumentsInfo.numberOfRemovedArguments() + + (method.isStatic() ? 0 : 1); + + int usedArgumentIndex = 0; + while (argumentIndex < numberOfArguments) { + TypeLatticeElement type; + if (removedArgumentsInfo.isArgumentRemoved(argumentIndex)) { + RemovedArgumentInfo argumentInfo = removedArgumentsInfo.getArgumentInfo(argumentIndex); + writeCallback.accept(register, argumentInfo.getType()); + type = + TypeLatticeElement.fromDexType( + argumentInfo.getType(), Nullability.maybeNull(), appView); + addConstantOrUnusedArgument(register, argumentInfo); + } else { + DexType dexType = method.method.proto.parameters.values[usedArgumentIndex++]; + writeCallback.accept(register, dexType); + type = TypeLatticeElement.fromDexType(dexType, Nullability.maybeNull(), appView); + if (dexType.isBooleanType()) { + addBooleanNonThisArgument(register); + } else { + addNonThisArgument(register, type); + } + } + register += type.requiredRegisters(); + argumentIndex++; + } + flushArgumentInstructions(); + } + /** * Build the high-level IR in SSA form. * @@ -864,7 +914,7 @@ public void addThisArgument(int register, TypeLatticeElement receiverType) { DebugLocalInfo local = getOutgoingLocal(register); Value value = writeRegister(register, receiverType, ThrowingInfo.NO_THROW, local); - addInstruction(new Argument(value, false)); + addInstruction(new Argument(value, currentBlock.size(), false)); receiverValue = value; value.markAsThis(); } @@ -872,13 +922,13 @@ public void addNonThisArgument(int register, TypeLatticeElement typeLattice) { DebugLocalInfo local = getOutgoingLocal(register); Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local); - addNonThisArgument(new Argument(value, false)); + addNonThisArgument(new Argument(value, currentBlock.size(), false)); } public void addBooleanNonThisArgument(int register) { DebugLocalInfo local = getOutgoingLocal(register); Value value = writeRegister(register, getInt(), ThrowingInfo.NO_THROW, local); - addNonThisArgument(new Argument(value, true)); + addNonThisArgument(new Argument(value, currentBlock.size(), true)); } private void addNonThisArgument(Argument argument) {
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 94df8fb..ce65222 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
@@ -59,7 +59,6 @@ import com.android.tools.r8.ir.optimize.DeadCodeRemover; import com.android.tools.r8.ir.optimize.Devirtualizer; import com.android.tools.r8.ir.optimize.DynamicTypeOptimization; -import com.android.tools.r8.ir.optimize.EnumUnboxer; import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; @@ -72,11 +71,14 @@ import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter; import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization; import com.android.tools.r8.ir.optimize.classinliner.ClassInliner; +import com.android.tools.r8.ir.optimize.enums.EnumUnboxer; +import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; import com.android.tools.r8.ir.optimize.lambda.LambdaMerger; import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer; import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer; @@ -157,6 +159,7 @@ private final TypeChecker typeChecker; private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter; private final ServiceLoaderRewriter serviceLoaderRewriter; + private final EnumValueOptimizer enumValueOptimizer; private final EnumUnboxer enumUnboxer; // Assumers that will insert Assume instructions. @@ -246,6 +249,7 @@ this.stringSwitchRemover = null; this.serviceLoaderRewriter = null; this.methodOptimizationInfoCollector = null; + this.enumValueOptimizer = null; this.enumUnboxer = null; return; } @@ -325,6 +329,8 @@ ? new DesugaredLibraryAPIConverter( appView, Mode.ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED) : null; + this.enumValueOptimizer = + options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null; this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null; } else { this.classInliner = null; @@ -349,6 +355,7 @@ : null; this.serviceLoaderRewriter = null; this.methodOptimizationInfoCollector = null; + this.enumValueOptimizer = null; this.enumUnboxer = null; } this.stringSwitchRemover = @@ -664,6 +671,10 @@ // Assure that no more optimization feedback left after primary processing. assert feedback.noUpdatesLeft(); appView.setAllCodeProcessed(); + // All the code has been processed so the rewriting required by the lenses is done everywhere, + // we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new + // lenses with code rewriting are added. + graphLenseForIR = appView.clearCodeRewritings(); if (libraryMethodOverrideAnalysis != null) { libraryMethodOverrideAnalysis.finish(); @@ -694,6 +705,11 @@ } timing.end(); + // All the code that should be impacted by the lenses inserted between phase 1 and phase 2 + // have now been processed and rewritten, we clear code lens rewriting so that the class + // staticizer and phase 3 does not perform again the rewriting. + appView.clearCodeRewritings(); + // TODO(b/112831361): Implement support for staticizeClasses in CF backend. if (!options.isGeneratingClassFiles()) { printPhase("Class staticizer post processing"); @@ -702,6 +718,10 @@ feedback.updateVisibleOptimizationInfo(); } + // The class staticizer lens shall not be applied through lens code rewriting or it breaks + // the lambda merger. + appView.clearCodeRewritings(); + // Build a new application with jumbo string info. Builder<?> builder = application.builder(); builder.setHighestSortingString(highestSortingString); @@ -814,6 +834,7 @@ if (options.enableFieldAssignmentTracker) { fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback); } + delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness()); delayedOptimizationFeedback.updateVisibleOptimizationInfo(); onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute); onWaveDoneActions = null; @@ -1105,18 +1126,17 @@ codeRewriter.simplifyDebugLocals(code); } + if (appView.graphLense().hasCodeRewritings()) { + assert lensCodeRewriter != null; + timing.begin("Lens rewrite"); + lensCodeRewriter.rewrite(code, method); + timing.end(); + } + if (method.isProcessed()) { assert !appView.enableWholeProgramOptimizations() || !appView.appInfo().withLiveness().neverReprocess.contains(method.method); } else { - if (lensCodeRewriter != null) { - timing.begin("Lens rewrite"); - lensCodeRewriter.rewrite(code, method); - timing.end(); - } else { - assert appView.graphLense().isIdentityLense(); - } - if (lambdaRewriter != null) { timing.begin("Desugar lambdas"); lambdaRewriter.desugarLambdas(method, code); @@ -1173,10 +1193,10 @@ timing.end(); } - if (options.enableEnumValueOptimization) { + if (enumValueOptimizer != null) { assert appView.enableWholeProgramOptimizations(); timing.begin("Remove switch maps"); - codeRewriter.removeSwitchMaps(code); + enumValueOptimizer.removeSwitchMaps(code); timing.end(); } @@ -1233,12 +1253,17 @@ assert code.isConsistentSSA(); } + assert code.verifyTypes(appView); + if (devirtualizer != null) { assert code.verifyTypes(appView); timing.begin("Devirtualize invoke interface"); devirtualizer.devirtualizeInvokeInterface(code, method.method.holder); timing.end(); } + + assert code.verifyTypes(appView); + if (uninstantiatedTypeOptimization != null) { timing.begin("Rewrite uninstantiated types"); uninstantiatedTypeOptimization.rewrite(code); @@ -1246,14 +1271,15 @@ } assert code.verifyTypes(appView); + timing.begin("Remove trivial type checks/casts"); codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code); timing.end(); - if (options.enableEnumValueOptimization) { + if (enumValueOptimizer != null) { assert appView.enableWholeProgramOptimizations(); timing.begin("Rewrite constant enum methods"); - codeRewriter.rewriteConstantEnumMethodCalls(code); + enumValueOptimizer.rewriteConstantEnumMethodCalls(code); timing.end(); } @@ -1323,7 +1349,7 @@ timing.begin("Optimize class initializers"); ClassInitializerDefaultsResult classInitializerDefaultsResult = - classInitializerDefaultsOptimization.optimize(method, code); + classInitializerDefaultsOptimization.optimize(method, code, feedback); timing.end(); if (Log.ENABLED) { @@ -1364,6 +1390,7 @@ appView.withLiveness(), codeRewriter, stringOptimizer, + enumValueOptimizer, method, code, feedback, @@ -1563,18 +1590,19 @@ return; } - methodOptimizationInfoCollector - .collectMethodOptimizationInfo(code.method, code, feedback, dynamicTypeOptimization); - + InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos = null; if (method.isInitializer()) { if (method.isClassInitializer()) { StaticFieldValueAnalysis.run( appView, code, classInitializerDefaultsResult, feedback, code.method); } else { - InstanceFieldValueAnalysis.run( - appView, code, classInitializerDefaultsResult, feedback, code.method); + instanceFieldInitializationInfos = + InstanceFieldValueAnalysis.run( + appView, code, classInitializerDefaultsResult, feedback, code.method); } } + methodOptimizationInfoCollector.collectMethodOptimizationInfo( + code.method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos); } public void removeDeadCodeAndFinalizeIR(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index 246d865..fc50632 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -27,9 +27,8 @@ import com.android.tools.r8.graph.DexValue.DexValueType; import com.android.tools.r8.graph.GraphLense; import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult; -import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.graph.RewrittenPrototypeDescription; -import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo; +import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection; import com.android.tools.r8.graph.UseRegistry.MethodHandleUse; import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater; @@ -47,7 +46,6 @@ import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Invoke; -import com.android.tools.r8.ir.code.Invoke.Type; import com.android.tools.r8.ir.code.InvokeCustom; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeMethod; @@ -169,15 +167,11 @@ graphLense.lookupMethod(invokedMethod, method.method, invoke.getType()); DexMethod actualTarget = lenseLookup.getMethod(); Invoke.Type actualInvokeType = lenseLookup.getType(); - if (actualInvokeType == Type.VIRTUAL) { - actualTarget = - rebindVirtualInvokeToMostSpecific( - actualTarget, invoke.inValues().get(0), method.method.holder); - } if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) { RewrittenPrototypeDescription prototypeChanges = graphLense.lookupPrototypeChanges(actualTarget); - RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo(); + RemovedArgumentInfoCollection removedArgumentsInfo = + prototypeChanges.getRemovedArgumentInfoCollection(); ConstInstruction constantReturnMaterializingInstruction = null; if (prototypeChanges.hasBeenChangedToReturnVoid() && invoke.outValue() != null) { @@ -597,80 +591,6 @@ return newProto != oldProto ? new DexValueMethodType(newProto) : type; } - /** - * This rebinds invoke-virtual instructions to their most specific target. - * - * <p>As a simple example, consider the instruction "invoke-virtual A.foo(v0)", and assume that v0 - * is defined by an instruction "new-instance v0, B". If B is a subtype of A, and B overrides the - * method foo(), then we rewrite the invocation into "invoke-virtual B.foo(v0)". - * - * <p>If A.foo() ends up being unused, this helps to ensure that we can get rid of A.foo() - * entirely. Without this rewriting, we would have to keep A.foo() because the method is targeted. - */ - private DexMethod rebindVirtualInvokeToMostSpecific( - DexMethod target, Value receiver, DexType context) { - if (!receiver.getTypeLattice().isClassType()) { - return target; - } - DexEncodedMethod encodedTarget = appView.definitionFor(target); - if (encodedTarget == null - || !canInvokeTargetWithInvokeVirtual(encodedTarget) - || !hasAccessToInvokeTargetFromContext(encodedTarget, context)) { - // Don't rewrite this instruction as it could remove an error from the program. - return target; - } - DexType receiverType = - appView - .graphLense() - .lookupType(receiver.getTypeLattice().asClassTypeLatticeElement().getClassType()); - if (receiverType == target.holder) { - // Virtual invoke is already as specific as it can get. - return target; - } - ResolutionResult resolutionResult = appView.appInfo().resolveMethod(receiverType, target); - DexEncodedMethod newTarget = - resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null; - if (newTarget == null || newTarget.method == target) { - // Most likely due to a missing class, or invoke is already as specific as it gets. - return target; - } - DexClass newTargetClass = appView.definitionFor(newTarget.method.holder); - if (newTargetClass == null - || newTargetClass.isLibraryClass() - || !canInvokeTargetWithInvokeVirtual(newTarget) - || !hasAccessToInvokeTargetFromContext(newTarget, context)) { - // Not safe to invoke `newTarget` with virtual invoke from the current context. - return target; - } - return newTarget.method; - } - - private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) { - return target.isNonPrivateVirtualMethod() - && appView.isInterface(target.method.holder).isFalse(); - } - - private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) { - assert !target.accessFlags.isPrivate(); - DexType holder = target.method.holder; - if (holder == context) { - // It is always safe to invoke a method from the same enclosing class. - return true; - } - DexClass clazz = appView.definitionFor(holder); - if (clazz == null) { - // Conservatively report an illegal access. - return false; - } - if (holder.isSamePackage(context)) { - // The class must be accessible (note that we have already established that the method is not - // private). - return !clazz.accessFlags.isPrivate(); - } - // If the method is in another package, then the method and its holder must be public. - return clazz.accessFlags.isPublic() && target.accessFlags.isPublic(); - } - class InstructionReplacer { private final IRCode code;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java index fae9325..0831e6d 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -592,9 +592,9 @@ @Override DexEncodedMethod ensureAccessibility(boolean allowMethodModification) { // We already found the static method to be called, just relax its accessibility. - target.method.accessFlags.unsetPrivate(); - if (target.holder.isInterface()) { - target.method.accessFlags.setPublic(); + target.getMethod().accessFlags.unsetPrivate(); + if (target.getHolder().isInterface()) { + target.getMethod().accessFlags.setPublic(); } return null; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java index e35087a..1e02c2b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -45,6 +45,7 @@ import com.android.tools.r8.ir.code.StaticPut; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.IRConverter; +import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo; import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -91,32 +92,20 @@ } } - private class WaveDoneAction implements Action { + private static class WaveDoneAction implements Action { private final Map<DexEncodedField, DexValue> fieldsWithStaticValues = new IdentityHashMap<>(); - private final Set<DexField> noLongerWrittenFields = Sets.newIdentityHashSet(); - public WaveDoneAction( - Map<DexEncodedField, DexValue> fieldsWithStaticValues, - Set<DexField> noLongerWrittenFields) { + WaveDoneAction(Map<DexEncodedField, DexValue> fieldsWithStaticValues) { this.fieldsWithStaticValues.putAll(fieldsWithStaticValues); - this.noLongerWrittenFields.addAll(noLongerWrittenFields); } - public synchronized void join( - Map<DexEncodedField, DexValue> fieldsWithStaticValues, - Set<DexField> noLongerWrittenFields) { + public synchronized void join(Map<DexEncodedField, DexValue> fieldsWithStaticValues) { this.fieldsWithStaticValues.putAll(fieldsWithStaticValues); - this.noLongerWrittenFields.addAll(noLongerWrittenFields); } @Override public void execute() { - // Update AppInfo. - AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); - appViewWithLiveness.setAppInfo( - appViewWithLiveness.appInfo().withoutStaticFieldsWrites(noLongerWrittenFields)); - // Update static field values of classes. fieldsWithStaticValues.forEach(DexEncodedField::setStaticValue); } @@ -134,7 +123,8 @@ this.dexItemFactory = appView.dexItemFactory(); } - public ClassInitializerDefaultsResult optimize(DexEncodedMethod method, IRCode code) { + public ClassInitializerDefaultsResult optimize( + DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) { if (appView.options().debug || method.getOptimizationInfo().isReachabilitySensitive()) { return ClassInitializerDefaultsResult.empty(); } @@ -269,17 +259,21 @@ } } - // Finally, remove these fields from the set of assigned static fields. + // Remove these fields from the set of assigned static fields. + feedback.modifyAppInfoWithLiveness( + modifier -> candidates.forEach(modifier::removeWrittenField)); + + // Update the static value of the fields when the wave ends. synchronized (this) { if (waveDoneAction == null) { - waveDoneAction = new WaveDoneAction(fieldsWithStaticValues, candidates); + waveDoneAction = new WaveDoneAction(fieldsWithStaticValues); converter.addWaveDoneAction( () -> { waveDoneAction.execute(); waveDoneAction = null; }); } else { - waveDoneAction.join(fieldsWithStaticValues, candidates); + waveDoneAction.join(fieldsWithStaticValues); } } } else {
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 5b695a7..7f2cee9 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
@@ -16,7 +16,6 @@ import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexItemFactory.ThrowableMethods; import com.android.tools.r8.graph.DexMethod; @@ -57,11 +56,9 @@ import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; -import com.android.tools.r8.ir.code.JumpInstruction; import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.NewArrayEmpty; import com.android.tools.r8.ir.code.NewArrayFilledData; @@ -76,9 +73,7 @@ import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.code.Xor; import com.android.tools.r8.ir.conversion.IRConverter; -import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; -import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOutputMode; import com.android.tools.r8.utils.LongInterval; @@ -93,7 +88,6 @@ import com.google.common.collect.ListMultimap; import com.google.common.collect.Sets; import com.google.common.collect.Streams; -import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; @@ -1043,69 +1037,6 @@ } /** - * Inline the indirection of switch maps into the switch statement. - * <p> - * To ensure binary compatibility, javac generated code does not use ordinal values of enums - * directly in switch statements but instead generates a companion class that computes a mapping - * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can - * analyze these maps and inline the indirection into the switch map again. - * <p> - * In particular, we look for code of the form - * - * <blockquote><pre> - * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) { - * ... - * } - * </pre></blockquote> - */ - public void removeSwitchMaps(IRCode code) { - for (BasicBlock block : code.blocks) { - JumpInstruction exit = block.exit(); - // Pattern match a switch on a switch map as input. - if (exit.isIntSwitch()) { - IntSwitch switchInsn = exit.asIntSwitch(); - EnumSwitchInfo info = SwitchUtils.analyzeSwitchOverEnum(switchInsn, appView.withLiveness()); - if (info != null) { - Int2IntMap targetMap = new Int2IntArrayMap(); - for (int i = 0; i < switchInsn.numberOfKeys(); i++) { - assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex(); - EnumValueInfo valueInfo = - info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i))); - targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]); - } - int[] keys = targetMap.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]); - } - - IntSwitch newSwitch = - new IntSwitch( - info.ordinalInvoke.outValue(), - keys, - targets, - switchInsn.getFallthroughBlockIndex()); - // Replace the switch itself. - exit.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; - if (!arrayGet.outValue().hasUsers()) { - 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); - } - } - } - } - } - - /** * Rewrite all branch targets to the destination of trivial goto chains when possible. Does not * rewrite fallthrough targets as that would require block reordering and the transformation only * makes sense after SSA destruction where there are no phis. @@ -2966,80 +2897,6 @@ return true; } - public void rewriteConstantEnumMethodCalls(IRCode code) { - if (!code.metadata().mayHaveInvokeMethodWithReceiver()) { - return; - } - - InstructionListIterator iterator = code.instructionListIterator(); - while (iterator.hasNext()) { - Instruction current = iterator.next(); - - if (!current.isInvokeMethodWithReceiver()) { - continue; - } - InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver(); - DexMethod invokedMethod = methodWithReceiver.getInvokedMethod(); - boolean isOrdinalInvoke = invokedMethod == dexItemFactory.enumMethods.ordinal; - boolean isNameInvoke = invokedMethod == dexItemFactory.enumMethods.name; - boolean isToStringInvoke = invokedMethod == dexItemFactory.enumMethods.toString; - if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) { - continue; - } - - Value receiver = methodWithReceiver.getReceiver().getAliasedValue(); - if (receiver.isPhi()) { - continue; - } - Instruction definition = receiver.getDefinition(); - if (!definition.isStaticGet()) { - continue; - } - DexField enumField = definition.asStaticGet().getField(); - - Map<DexField, EnumValueInfo> valueInfoMap = - appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type); - if (valueInfoMap == null) { - continue; - } - - // The receiver value is identified as being from a constant enum field lookup by the fact - // that it is a static-get to a field whose type is the same as the enclosing class (which - // is known to be an enum type). An enum may still define a static field using the enum type - // so ensure the field is present in the ordinal map for final validation. - EnumValueInfo valueInfo = valueInfoMap.get(enumField); - if (valueInfo == null) { - continue; - } - - Value outValue = methodWithReceiver.outValue(); - if (isOrdinalInvoke) { - iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal)); - } else if (isNameInvoke) { - iterator.replaceCurrentInstruction( - new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW)); - } else { - assert isToStringInvoke; - DexClass enumClazz = appView.appInfo().definitionFor(enumField.type); - if (!enumClazz.accessFlags.isFinal()) { - continue; - } - if (appView - .appInfo() - .resolveMethodOnClass(valueInfo.type, dexItemFactory.objectMethods.toString) - .getSingleTarget() - .method - != dexItemFactory.enumMethods.toString) { - continue; - } - iterator.replaceCurrentInstruction( - new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW)); - } - } - - assert code.isConsistentSSA(); - } - public void rewriteKnownArrayLengthCalls(IRCode code) { InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java index 8370b69..f1c5dc9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -607,11 +607,11 @@ for (Monitor monitor : inlinee.code.<Monitor>instructions(Instruction::isMonitorEnter)) { Value monitorEnterValue = monitor.object().getAliasedValue(); - if (monitorEnterValue.isArgument()) { + if (monitorEnterValue.isDefinedByInstructionSatisfying(Instruction::isArgument)) { monitorEnterValue = invoke .arguments() - .get(monitorEnterValue.computeArgumentPosition(inlinee.code)) + .get(monitorEnterValue.definition.asArgument().getIndex()) .getAliasedValue(); } addMonitorEnterValue(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java index bebbbf4..0c53a4c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -6,7 +6,9 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; import com.android.tools.r8.ir.code.Assume; @@ -17,6 +19,7 @@ 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.Invoke; import com.android.tools.r8.ir.code.InvokeInterface; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Value; @@ -31,8 +34,14 @@ import java.util.Set; /** - * Rewrites all invoke-interface instructions that have a unique target on a class into + * Tries to rewrite virtual invokes to their most specific target by: + * + * <pre> + * 1) Rewriting all invoke-interface instructions that have a unique target on a class into * invoke-virtual with the corresponding unique target. + * 2) Rewriting all invoke-virtual instructions that have a more specific target to an + * invoke-virtual with the corresponding target. + * </pre> */ public class Devirtualizer { @@ -105,6 +114,20 @@ } } + if (current.isInvokeVirtual()) { + InvokeVirtual invoke = current.asInvokeVirtual(); + DexMethod invokedMethod = invoke.getInvokedMethod(); + DexMethod reboundTarget = + rebindVirtualInvokeToMostSpecific( + invokedMethod, invoke.getReceiver(), invocationContext); + if (reboundTarget != invokedMethod) { + Invoke newInvoke = + Invoke.create( + Invoke.Type.VIRTUAL, reboundTarget, null, invoke.outValue(), invoke.inValues()); + it.replaceCurrentInstruction(newInvoke); + } + } + if (!current.isInvokeInterface()) { continue; } @@ -226,4 +249,78 @@ } assert code.isConsistentSSA(); } + + /** + * This rebinds invoke-virtual instructions to their most specific target. + * + * <p>As a simple example, consider the instruction "invoke-virtual A.foo(v0)", and assume that v0 + * is defined by an instruction "new-instance v0, B". If B is a subtype of A, and B overrides the + * method foo(), then we rewrite the invocation into "invoke-virtual B.foo(v0)". + * + * <p>If A.foo() ends up being unused, this helps to ensure that we can get rid of A.foo() + * entirely. Without this rewriting, we would have to keep A.foo() because the method is targeted. + */ + private DexMethod rebindVirtualInvokeToMostSpecific( + DexMethod target, Value receiver, DexType context) { + if (!receiver.getTypeLattice().isClassType()) { + return target; + } + DexEncodedMethod encodedTarget = appView.definitionFor(target); + if (encodedTarget == null + || !canInvokeTargetWithInvokeVirtual(encodedTarget) + || !hasAccessToInvokeTargetFromContext(encodedTarget, context)) { + // Don't rewrite this instruction as it could remove an error from the program. + return target; + } + DexType receiverType = + appView + .graphLense() + .lookupType(receiver.getTypeLattice().asClassTypeLatticeElement().getClassType()); + if (receiverType == target.holder) { + // Virtual invoke is already as specific as it can get. + return target; + } + ResolutionResult resolutionResult = appView.appInfo().resolveMethod(receiverType, target); + DexEncodedMethod newTarget = + resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null; + if (newTarget == null || newTarget.method == target) { + // Most likely due to a missing class, or invoke is already as specific as it gets. + return target; + } + DexClass newTargetClass = appView.definitionFor(newTarget.method.holder); + if (newTargetClass == null + || newTargetClass.isLibraryClass() + || !canInvokeTargetWithInvokeVirtual(newTarget) + || !hasAccessToInvokeTargetFromContext(newTarget, context)) { + // Not safe to invoke `newTarget` with virtual invoke from the current context. + return target; + } + return newTarget.method; + } + + private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) { + return target.isNonPrivateVirtualMethod() + && appView.isInterface(target.method.holder).isFalse(); + } + + private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) { + assert !target.accessFlags.isPrivate(); + DexType holder = target.method.holder; + if (holder == context) { + // It is always safe to invoke a method from the same enclosing class. + return true; + } + DexClass clazz = appView.definitionFor(holder); + if (clazz == null) { + // Conservatively report an illegal access. + return false; + } + if (holder.isSamePackage(context)) { + // The class must be accessible (note that we have already established that the method is not + // private). + return !clazz.accessFlags.isPrivate(); + } + // If the method is in another package, then the method and its holder must be public. + return clazz.accessFlags.isPublic() && target.accessFlags.isPublic(); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java index 96819d0..70a9795 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -442,7 +442,9 @@ // For each of the actual potential targets, derive constraints based on the accessibility // of the method itself. - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + LookupResult lookupResult = + resolutionResult.lookupVirtualDispatchTargets( + appView.definitionForProgramType(invocationContext), appView); if (lookupResult.isLookupResultFailure()) { return ConstraintWithTarget.NEVER; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java index aeedc02..6e53d40 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -4,8 +4,8 @@ package com.android.tools.r8.ir.optimize; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.Descriptor; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexMember; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -28,13 +28,13 @@ import java.util.function.Predicate; // Per-class collection of member signatures. -public abstract class MemberPoolCollection<T extends Descriptor> { +public abstract class MemberPoolCollection<R extends DexMember<?, R>> { - final Equivalence<T> equivalence; + final Equivalence<R> equivalence; final AppView<AppInfoWithLiveness> appView; - final Map<DexClass, MemberPool<T>> memberPools = new ConcurrentHashMap<>(); + final Map<DexClass, MemberPool<R>> memberPools = new ConcurrentHashMap<>(); - MemberPoolCollection(AppView<AppInfoWithLiveness> appView, Equivalence<T> equivalence) { + MemberPoolCollection(AppView<AppInfoWithLiveness> appView, Equivalence<R> equivalence) { this.appView = appView; this.equivalence = equivalence; } @@ -56,7 +56,7 @@ } } - public MemberPool<T> buildForHierarchy( + public MemberPool<R> buildForHierarchy( DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException { timing.begin("Building member pool collection"); try { @@ -75,14 +75,14 @@ return memberPools.containsKey(clazz); } - public MemberPool<T> get(DexClass clazz) { + public MemberPool<R> get(DexClass clazz) { assert hasPool(clazz); return memberPools.get(clazz); } - public boolean markIfNotSeen(DexClass clazz, T reference) { - MemberPool<T> memberPool = get(clazz); - Wrapper<T> key = equivalence.wrap(reference); + public boolean markIfNotSeen(DexClass clazz, R reference) { + MemberPool<R> memberPool = get(clazz); + Wrapper<R> key = equivalence.wrap(reference); if (memberPool.hasSeen(key)) { return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java index 5577a8c..f362ef7 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -186,7 +186,8 @@ if (newInstance.clazz.classInitializationMayHaveSideEffects( appView, // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context, type).isTrue())) { + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet())) { killAllActiveFields(); } } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java deleted file mode 100644 index 0dd0bcf..0000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java +++ /dev/null
@@ -1,106 +0,0 @@ -// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.ir.optimize; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexField; -import com.android.tools.r8.graph.DexItemFactory; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.ir.code.ArrayGet; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InvokeVirtual; -import com.android.tools.r8.ir.code.StaticGet; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; -import java.util.Map; - -public class SwitchUtils { - - public static final class EnumSwitchInfo { - public final DexType enumClass; - public final Instruction ordinalInvoke; - public final Instruction arrayGet; - public final Instruction staticGet; - public final Int2ReferenceMap<DexField> indexMap; - public final Map<DexField, EnumValueInfo> valueInfoMap; - - private EnumSwitchInfo(DexType enumClass, - Instruction ordinalInvoke, - Instruction arrayGet, Instruction staticGet, - Int2ReferenceMap<DexField> indexMap, - Map<DexField, EnumValueInfo> valueInfoMap) { - this.enumClass = enumClass; - this.ordinalInvoke = ordinalInvoke; - this.arrayGet = arrayGet; - this.staticGet = staticGet; - this.indexMap = indexMap; - this.valueInfoMap = valueInfoMap; - } - } - - /** - * Looks for a switch statement over the enum companion class of the form - * - * <blockquote> - * - * <pre> - * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) { - * ... - * } - * </pre> - * - * </blockquote> - * - * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector} - * and {@link SwitchMapCollector} for details. - */ - public static EnumSwitchInfo analyzeSwitchOverEnum( - Instruction switchInsn, AppView<AppInfoWithLiveness> appView) { - AppInfoWithLiveness appInfo = appView.appInfo(); - Instruction input = switchInsn.inValues().get(0).definition; - if (input == null || !input.isArrayGet()) { - return null; - } - ArrayGet arrayGet = input.asArrayGet(); - Instruction index = arrayGet.index().definition; - if (index == null || !index.isInvokeVirtual()) { - return null; - } - InvokeVirtual ordinalInvoke = index.asInvokeVirtual(); - DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod(); - DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder); - DexItemFactory dexItemFactory = appInfo.dexItemFactory(); - // After member rebinding, enumClass will be the actual java.lang.Enum class. - if (enumClass == null - || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType) - || ordinalMethod.name != dexItemFactory.ordinalMethodName - || ordinalMethod.proto.returnType != dexItemFactory.intType - || !ordinalMethod.proto.parameters.isEmpty()) { - return null; - } - Instruction array = arrayGet.array().definition; - if (array == null || !array.isStaticGet()) { - return null; - } - StaticGet staticGet = array.asStaticGet(); - Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField()); - if (indexMap == null || indexMap.isEmpty()) { - return null; - } - // Due to member rebinding, only the fields are certain to provide the actual enums - // class. - DexType enumType = indexMap.values().iterator().next().holder; - Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType); - if (valueInfoMap == null) { - return null; - } - return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap, - valueInfoMap); - } - - -}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java index c234bc3..a04e7c5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -20,7 +20,7 @@ import com.android.tools.r8.graph.GraphLense.NestedGraphLense; import com.android.tools.r8.graph.RewrittenPrototypeDescription; import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo; -import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo; +import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection; import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; import com.android.tools.r8.ir.analysis.AbstractError; import com.android.tools.r8.ir.analysis.TypeChecker; @@ -43,7 +43,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; import java.util.HashSet; @@ -63,11 +62,11 @@ public static class UninstantiatedTypeOptimizationGraphLense extends NestedGraphLense { - private final Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod; + private final Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod; UninstantiatedTypeOptimizationGraphLense( BiMap<DexMethod, DexMethod> methodMap, - Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod, + Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod, AppView<?> appView) { super( ImmutableMap.of(), @@ -88,7 +87,8 @@ if (method.proto.returnType.isVoidType() && !originalMethod.proto.returnType.isVoidType()) { result = result.withConstantReturn(); } - RemovedArgumentsInfo removedArgumentsInfo = removedArgumentsInfoPerMethod.get(method); + RemovedArgumentInfoCollection removedArgumentsInfo = + removedArgumentsInfoPerMethod.get(method); if (removedArgumentsInfo != null) { result = result.withRemovedArguments(removedArgumentsInfo); } @@ -125,7 +125,8 @@ Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods = new HashMap<>(); BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create(); - Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod = new IdentityHashMap<>(); + Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod = + new IdentityHashMap<>(); TopDownClassHierarchyTraversal.forProgramClasses(appView) .visit( @@ -150,7 +151,7 @@ Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods, BiMap<DexMethod, DexMethod> methodMapping, MethodPoolCollection methodPoolCollection, - Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod) { + Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod) { MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz); if (clazz.isInterface()) { @@ -198,7 +199,8 @@ RewrittenPrototypeDescription prototypeChanges = prototypeChangesPerMethod.getOrDefault( encodedMethod, RewrittenPrototypeDescription.none()); - RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo(); + RemovedArgumentInfoCollection removedArgumentsInfo = + prototypeChanges.getRemovedArgumentInfoCollection(); DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges); if (newMethod != method) { Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod); @@ -231,7 +233,8 @@ DexMethod method = encodedMethod.method; RewrittenPrototypeDescription prototypeChanges = getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL); - RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo(); + RemovedArgumentInfoCollection removedArgumentsInfo = + prototypeChanges.getRemovedArgumentInfoCollection(); DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges); if (newMethod != method) { Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod); @@ -259,7 +262,8 @@ DexMethod method = encodedMethod.method; RewrittenPrototypeDescription prototypeChanges = getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL); - RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo(); + RemovedArgumentInfoCollection removedArgumentsInfo = + prototypeChanges.getRemovedArgumentInfoCollection(); DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges); if (newMethod != method) { Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod); @@ -298,32 +302,24 @@ getRemovedArgumentsInfo(encodedMethod, strategy)); } - private RemovedArgumentsInfo getRemovedArgumentsInfo( + private RemovedArgumentInfoCollection getRemovedArgumentsInfo( DexEncodedMethod encodedMethod, Strategy strategy) { if (strategy == DISALLOW_ARGUMENT_REMOVAL) { - return RemovedArgumentsInfo.empty(); + return RemovedArgumentInfoCollection.empty(); } - List<RemovedArgumentInfo> removedArgumentsInfo = null; + RemovedArgumentInfoCollection.Builder argInfosBuilder = RemovedArgumentInfoCollection.builder(); DexProto proto = encodedMethod.method.proto; int offset = encodedMethod.isStatic() ? 0 : 1; for (int i = 0; i < proto.parameters.size(); ++i) { DexType type = proto.parameters.values[i]; if (type.isAlwaysNull(appView)) { - if (removedArgumentsInfo == null) { - removedArgumentsInfo = new ArrayList<>(); - } - removedArgumentsInfo.add( - RemovedArgumentInfo.builder() - .setArgumentIndex(i + offset) - .setIsAlwaysNull() - .setType(type) - .build()); + RemovedArgumentInfo removedArg = + RemovedArgumentInfo.builder().setIsAlwaysNull().setType(type).build(); + argInfosBuilder.addRemovedArgument(i + offset, removedArg); } } - return removedArgumentsInfo != null - ? new RemovedArgumentsInfo(removedArgumentsInfo) - : RemovedArgumentsInfo.empty(); + return argInfosBuilder.build(); } private DexMethod getNewMethodSignature( @@ -336,6 +332,7 @@ } public void rewrite(IRCode code) { + AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code); Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet(); ListIterator<BasicBlock> blockIterator = code.listIterator(); Set<Value> valuesToNarrow = Sets.newIdentityHashSet(); @@ -375,6 +372,7 @@ blockIterator, instructionIterator, code, + assumeDynamicTypeRemover, valuesToNarrow); } else if (instruction.isInvokeMethod()) { rewriteInvoke( @@ -382,11 +380,13 @@ blockIterator, instructionIterator, code, + assumeDynamicTypeRemover, blocksToBeRemoved, valuesToNarrow); } } } + assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); code.removeBlocks(blocksToBeRemoved); code.removeAllTrivialPhis(valuesToNarrow); code.removeUnreachableBlocks(); @@ -444,6 +444,7 @@ ListIterator<BasicBlock> blockIterator, InstructionListIterator instructionIterator, IRCode code, + AssumeDynamicTypeRemover assumeDynamicTypeRemover, Set<Value> affectedValues) { DexType context = code.method.method.holder; DexField field = instruction.getField(); @@ -473,10 +474,12 @@ } else { if (instructionCanBeRemoved) { // Replace the field read by the constant null. + assumeDynamicTypeRemover.markUsersForRemoval(instruction.outValue()); affectedValues.addAll(instruction.outValue().affectedValues()); instructionIterator.replaceCurrentInstruction(code.createConstNull()); } else { - replaceOutValueByNull(instruction, instructionIterator, code, affectedValues); + replaceOutValueByNull( + instruction, instructionIterator, code, assumeDynamicTypeRemover, affectedValues); } } @@ -496,6 +499,7 @@ ListIterator<BasicBlock> blockIterator, InstructionListIterator instructionIterator, IRCode code, + AssumeDynamicTypeRemover assumeDynamicTypeRemover, Set<BasicBlock> blocksToBeRemoved, Set<Value> affectedValues) { DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.method.holder); @@ -518,7 +522,8 @@ DexType returnType = target.method.proto.returnType; if (returnType.isAlwaysNull(appView)) { - replaceOutValueByNull(invoke, instructionIterator, code, affectedValues); + replaceOutValueByNull( + invoke, instructionIterator, code, assumeDynamicTypeRemover, affectedValues); } } @@ -526,11 +531,13 @@ Instruction instruction, InstructionListIterator instructionIterator, IRCode code, + AssumeDynamicTypeRemover assumeDynamicTypeRemover, Set<Value> affectedValues) { assert instructionIterator.peekPrevious() == instruction; if (instruction.hasOutValue()) { Value outValue = instruction.outValue(); if (outValue.numberOfAllUsers() > 0) { + assumeDynamicTypeRemover.markUsersForRemoval(outValue); instructionIterator.previous(); affectedValues.addAll(outValue.affectedValues()); outValue.replaceUsers(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java index d8568d5..e99ba9a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -18,7 +18,7 @@ import com.android.tools.r8.graph.GraphLense.NestedGraphLense; import com.android.tools.r8.graph.RewrittenPrototypeDescription; import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo; -import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo; +import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection; import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.MethodSignatureEquivalence; @@ -32,7 +32,6 @@ import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Streams; -import java.util.ArrayList; import java.util.BitSet; import java.util.HashSet; import java.util.IdentityHashMap; @@ -51,11 +50,12 @@ private final MethodPoolCollection methodPoolCollection; private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create(); - private final Map<DexMethod, RemovedArgumentsInfo> removedArguments = new IdentityHashMap<>(); + private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments = + new IdentityHashMap<>(); public static class UnusedArgumentsGraphLense extends NestedGraphLense { - private final Map<DexMethod, RemovedArgumentsInfo> removedArguments; + private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments; UnusedArgumentsGraphLense( Map<DexType, DexType> typeMap, @@ -65,7 +65,7 @@ BiMap<DexMethod, DexMethod> originalMethodSignatures, GraphLense previousLense, DexItemFactory dexItemFactory, - Map<DexMethod, RemovedArgumentsInfo> removedArguments) { + Map<DexMethod, RemovedArgumentInfoCollection> removedArguments) { super( typeMap, methodMap, @@ -84,7 +84,7 @@ ? originalMethodSignatures.getOrDefault(method, method) : method; RewrittenPrototypeDescription result = previousLense.lookupPrototypeChanges(originalMethod); - RemovedArgumentsInfo removedArguments = this.removedArguments.get(method); + RemovedArgumentInfoCollection removedArguments = this.removedArguments.get(method); return removedArguments != null ? result.withRemovedArguments(removedArguments) : result; } } @@ -167,7 +167,7 @@ } DexEncodedMethod removeArguments( - DexEncodedMethod method, DexMethod newSignature, RemovedArgumentsInfo unused) { + DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) { boolean removed = usedSignatures.remove(equivalence.wrap(method.method)); assert removed; @@ -208,7 +208,7 @@ } DexEncodedMethod removeArguments( - DexEncodedMethod method, DexMethod newSignature, RemovedArgumentsInfo unused) { + DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) { methodPool.seen(equivalence.wrap(newSignature)); return method.toTypeSubstitutedMethod( newSignature, unused.createParameterAnnotationsRemover(method)); @@ -234,7 +234,7 @@ continue; } - RemovedArgumentsInfo unused = collectUnusedArguments(method); + RemovedArgumentInfoCollection unused = collectUnusedArguments(method); if (unused != null && unused.hasRemovedArguments()) { DexProto newProto = createProtoWithRemovedArguments(method, unused); DexMethod newSignature = signatures.getNewSignature(method, newProto); @@ -259,7 +259,7 @@ List<DexEncodedMethod> virtualMethods = clazz.virtualMethods(); for (int i = 0; i < virtualMethods.size(); i++) { DexEncodedMethod method = virtualMethods.get(i); - RemovedArgumentsInfo unused = collectUnusedArguments(method, methodPool); + RemovedArgumentInfoCollection unused = collectUnusedArguments(method, methodPool); if (unused != null && unused.hasRemovedArguments()) { DexProto newProto = createProtoWithRemovedArguments(method, unused); DexMethod newSignature = signatures.getNewSignature(method, newProto); @@ -279,11 +279,11 @@ } } - private RemovedArgumentsInfo collectUnusedArguments(DexEncodedMethod method) { + private RemovedArgumentInfoCollection collectUnusedArguments(DexEncodedMethod method) { return collectUnusedArguments(method, null); } - private RemovedArgumentsInfo collectUnusedArguments( + private RemovedArgumentInfoCollection collectUnusedArguments( DexEncodedMethod method, MemberPool<DexMethod> methodPool) { if (ArgumentRemovalUtils.isPinned(method, appView) || appView.appInfo().keepUnusedArguments.contains(method.method)) { @@ -314,23 +314,24 @@ method.getCode().registerArgumentReferences(method, collector); BitSet used = collector.getUsedArguments(); if (used.cardinality() < argumentCount) { - List<RemovedArgumentInfo> unused = new ArrayList<>(); + RemovedArgumentInfoCollection.Builder argInfosBuilder = + RemovedArgumentInfoCollection.builder(); for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) { if (!used.get(argumentIndex)) { - unused.add( + RemovedArgumentInfo removedArg = RemovedArgumentInfo.builder() - .setArgumentIndex(argumentIndex) .setType(method.method.proto.parameters.values[argumentIndex - offset]) - .build()); + .build(); + argInfosBuilder.addRemovedArgument(argumentIndex, removedArg); } } - return new RemovedArgumentsInfo(unused); + return argInfosBuilder.build(); } return null; } private DexProto createProtoWithRemovedArguments( - DexEncodedMethod encodedMethod, RemovedArgumentsInfo unused) { + DexEncodedMethod encodedMethod, RemovedArgumentInfoCollection unused) { DexType[] parameters = unused.rewriteParameters(encodedMethod); return appView.dexItemFactory().createProto(encodedMethod.method.proto.returnType, parameters); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index c4a761b..b20eba1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -22,6 +22,7 @@ import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.InliningOracle; import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException; +import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider; import com.android.tools.r8.ir.optimize.string.StringOptimizer; @@ -164,6 +165,7 @@ AppView<AppInfoWithLiveness> appView, CodeRewriter codeRewriter, StringOptimizer stringOptimizer, + EnumValueOptimizer enumValueOptimizer, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, @@ -282,7 +284,7 @@ appView.withGeneratedMessageLiteBuilderShrinker( shrinker -> shrinker.inlineCallsToDynamicMethod( - method, code, codeRewriter, feedback, methodProcessor, inliner)); + method, code, enumValueOptimizer, feedback, methodProcessor, inliner)); } if (anyInlinedMethods) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java index 9231d05..f076b41 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -160,7 +160,8 @@ if (eligibleClass.classInitializationMayHaveSideEffects( appView, // Types that are a super type of the current context are guaranteed to be initialized. - type -> appView.isSubtype(method.method.holder, type).isTrue())) { + type -> appView.isSubtype(method.method.holder, type).isTrue(), + Sets.newIdentityHashSet())) { return EligibilityStatus.HAS_CLINIT; } return EligibilityStatus.ELIGIBLE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java similarity index 96% rename from src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java index 2472589..e0991e0 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
@@ -1,7 +1,7 @@ -// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// 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; +package com.android.tools.r8.ir.optimize.enums; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java similarity index 96% rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java index c38b7b0..b3a25d0 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.ir.optimize; +package com.android.tools.r8.ir.optimize.enums; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; @@ -143,7 +143,13 @@ assert enumClass != null; DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor); - assert initializer != null; + if (initializer == null) { + // This case typically happens when a programmer uses EnumSet/EnumMap without using the + // enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work). + // We bail out. + markEnumAsUnboxable(Reason.NO_INIT, enumClass); + continue; + } if (initializer.getOptimizationInfo().mayHaveSideEffects()) { markEnumAsUnboxable(Reason.INVALID_INIT, enumClass); continue; @@ -325,6 +331,7 @@ VIRTUAL_METHOD, UNEXPECTED_DIRECT_METHOD, INVALID_PHI, + NO_INIT, INVALID_INIT, INVALID_CLINIT, INVALID_INVOKE,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java similarity index 97% rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java index 3fb84e1..7b207c6 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.ir.optimize; +package com.android.tools.r8.ir.optimize.enums; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedField; @@ -10,7 +10,7 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.ir.optimize.EnumUnboxer.Reason; +import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason; import com.google.common.collect.Sets; import java.util.Set;
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 new file mode 100644 index 0000000..c8122470 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.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.ir.optimize.enums; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +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; +import com.android.tools.r8.ir.code.ConstNumber; +import com.android.tools.r8.ir.code.ConstString; +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.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.shaking.AppInfoWithLiveness.EnumValueInfo; +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; +import java.util.Arrays; +import java.util.Map; + +public class EnumValueOptimizer { + + private final AppView<AppInfoWithLiveness> appView; + private final DexItemFactory factory; + + public EnumValueOptimizer(AppView<AppInfoWithLiveness> appView) { + this.appView = appView; + this.factory = appView.dexItemFactory(); + } + + @SuppressWarnings("ConstantConditions") + public void rewriteConstantEnumMethodCalls(IRCode code) { + if (!code.metadata().mayHaveInvokeMethodWithReceiver()) { + return; + } + + InstructionListIterator iterator = code.instructionListIterator(); + while (iterator.hasNext()) { + Instruction current = iterator.next(); + + if (!current.isInvokeMethodWithReceiver()) { + continue; + } + InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver(); + DexMethod invokedMethod = methodWithReceiver.getInvokedMethod(); + boolean isOrdinalInvoke = invokedMethod == factory.enumMethods.ordinal; + boolean isNameInvoke = invokedMethod == factory.enumMethods.name; + boolean isToStringInvoke = invokedMethod == factory.enumMethods.toString; + if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) { + continue; + } + + Value receiver = methodWithReceiver.getReceiver().getAliasedValue(); + if (receiver.isPhi()) { + continue; + } + Instruction definition = receiver.getDefinition(); + if (!definition.isStaticGet()) { + continue; + } + DexField enumField = definition.asStaticGet().getField(); + + Map<DexField, EnumValueInfo> valueInfoMap = + appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type); + if (valueInfoMap == null) { + continue; + } + + // The receiver value is identified as being from a constant enum field lookup by the fact + // that it is a static-get to a field whose type is the same as the enclosing class (which + // is known to be an enum type). An enum may still define a static field using the enum type + // so ensure the field is present in the ordinal map for final validation. + EnumValueInfo valueInfo = valueInfoMap.get(enumField); + if (valueInfo == null) { + continue; + } + + Value outValue = methodWithReceiver.outValue(); + if (isOrdinalInvoke) { + iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal)); + } else if (isNameInvoke) { + iterator.replaceCurrentInstruction( + new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW)); + } else { + assert isToStringInvoke; + DexClass enumClazz = appView.appInfo().definitionFor(enumField.type); + if (!enumClazz.accessFlags.isFinal()) { + continue; + } + DexEncodedMethod singleTarget = + appView + .appInfo() + .resolveMethodOnClass(valueInfo.type, factory.objectMethods.toString) + .getSingleTarget(); + if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) { + continue; + } + iterator.replaceCurrentInstruction( + new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW)); + } + } + assert code.isConsistentSSA(); + } + + /** + * Inline the indirection of switch maps into the switch statement. + * + * <p>To ensure binary compatibility, javac generated code does not use ordinal values of enums + * directly in switch statements but instead generates a companion class that computes a mapping + * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can analyze + * these maps and inline the indirection into the switch map again. + * + * <p>In particular, we look for code of the form + * + * <blockquote> + * + * <pre> + * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) { + * ... + * } + * </pre> + * + * </blockquote> + */ + public void removeSwitchMaps(IRCode code) { + for (BasicBlock block : code.blocks) { + JumpInstruction exit = block.exit(); + // Pattern match a switch on a switch map as input. + if (!exit.isIntSwitch()) { + continue; + } + IntSwitch switchInsn = exit.asIntSwitch(); + EnumSwitchInfo info = analyzeSwitchOverEnum(switchInsn); + if (info == null) { + continue; + } + Int2IntMap targetMap = new Int2IntArrayMap(); + for (int i = 0; i < switchInsn.numberOfKeys(); i++) { + assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex(); + EnumValueInfo valueInfo = info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i))); + targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]); + } + int[] keys = targetMap.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]); + } + + IntSwitch newSwitch = + new IntSwitch( + info.ordinalInvoke.outValue(), keys, targets, switchInsn.getFallthroughBlockIndex()); + // Replace the switch itself. + exit.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; + if (!arrayGet.outValue().hasUsers()) { + 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); + } + } + } + + private static final class EnumSwitchInfo { + + final DexType enumClass; + final Instruction ordinalInvoke; + final Instruction arrayGet; + public final Instruction staticGet; + final Int2ReferenceMap<DexField> indexMap; + final Map<DexField, EnumValueInfo> valueInfoMap; + + private EnumSwitchInfo( + DexType enumClass, + Instruction ordinalInvoke, + Instruction arrayGet, + Instruction staticGet, + Int2ReferenceMap<DexField> indexMap, + Map<DexField, EnumValueInfo> valueInfoMap) { + this.enumClass = enumClass; + this.ordinalInvoke = ordinalInvoke; + this.arrayGet = arrayGet; + this.staticGet = staticGet; + this.indexMap = indexMap; + this.valueInfoMap = valueInfoMap; + } + } + + /** + * Looks for a switch statement over the enum companion class of the form + * + * <blockquote> + * + * <pre> + * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) { + * ... + * } + * </pre> + * + * </blockquote> + * + * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector} + * and {@link SwitchMapCollector} for details. + */ + private EnumSwitchInfo analyzeSwitchOverEnum(Instruction switchInsn) { + AppInfoWithLiveness appInfo = appView.appInfo(); + Instruction input = switchInsn.inValues().get(0).definition; + if (input == null || !input.isArrayGet()) { + return null; + } + ArrayGet arrayGet = input.asArrayGet(); + Instruction index = arrayGet.index().definition; + if (index == null || !index.isInvokeVirtual()) { + return null; + } + InvokeVirtual ordinalInvoke = index.asInvokeVirtual(); + DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod(); + DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder); + DexItemFactory dexItemFactory = appInfo.dexItemFactory(); + // After member rebinding, enumClass will be the actual java.lang.Enum class. + if (enumClass == null + || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType) + || ordinalMethod.name != dexItemFactory.ordinalMethodName + || ordinalMethod.proto.returnType != dexItemFactory.intType + || !ordinalMethod.proto.parameters.isEmpty()) { + return null; + } + Instruction array = arrayGet.array().definition; + if (array == null || !array.isStaticGet()) { + return null; + } + StaticGet staticGet = array.asStaticGet(); + Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField()); + if (indexMap == null || indexMap.isEmpty()) { + return null; + } + // Due to member rebinding, only the fields are certain to provide the actual enums + // class. + DexType enumType = indexMap.values().iterator().next().holder; + Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType); + if (valueInfoMap == null) { + return null; + } + return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap, valueInfoMap); + } +}
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 d1a6c3a..b5215f6 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
@@ -84,6 +84,7 @@ import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis; import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage; import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo; @@ -120,7 +121,8 @@ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, - DynamicTypeOptimization dynamicTypeOptimization) { + DynamicTypeOptimization dynamicTypeOptimization, + InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) { identifyClassInlinerEligibility(method, code, feedback); identifyParameterUsages(method, code, feedback); identifyReturnsArgument(method, code, feedback); @@ -129,7 +131,7 @@ } computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code); computeInitializedClassesOnNormalExit(feedback, method, code); - computeInstanceInitializerInfo(method, code, feedback); + computeInstanceInitializerInfo(method, code, feedback, instanceFieldInitializationInfos); computeMayHaveSideEffects(feedback, method, code); computeReturnValueOnlyDependsOnArguments(feedback, method, code); computeNonNullParamOrThrow(feedback, method, code); @@ -336,10 +338,7 @@ if (!aliasedValue.isPhi()) { Instruction definition = aliasedValue.definition; if (definition.isArgument()) { - // Find the argument number. - int index = aliasedValue.computeArgumentPosition(code); - assert index >= 0; - feedback.methodReturnsArgument(method, index); + feedback.methodReturnsArgument(method, definition.asArgument().getIndex()); } DexType context = method.method.holder; AbstractValue abstractReturnValue = definition.getAbstractValue(appView, context); @@ -354,13 +353,18 @@ } private void computeInstanceInitializerInfo( - DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) { + DexEncodedMethod method, + IRCode code, + OptimizationFeedback feedback, + InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) { assert !appView.appInfo().isPinned(method.method); if (!method.isInstanceInitializer()) { return; } + assert instanceFieldInitializationInfos != null; + if (method.accessFlags.isNative()) { return; } @@ -375,7 +379,10 @@ return; } - InstanceInitializerInfo instanceInitializerInfo = analyzeInstanceInitializer(code, clazz); + NonTrivialInstanceInitializerInfo.Builder builder = + NonTrivialInstanceInitializerInfo.builder(instanceFieldInitializationInfos); + InstanceInitializerInfo instanceInitializerInfo = + analyzeInstanceInitializer(code, clazz, builder); feedback.setInstanceInitializerInfo( method, instanceInitializerInfo != null @@ -398,13 +405,13 @@ // ** Assigns arguments or non-throwing constants to fields of this class. // // (Note that this initializer does not have to have zero arguments.) - private InstanceInitializerInfo analyzeInstanceInitializer(IRCode code, DexClass clazz) { + private InstanceInitializerInfo analyzeInstanceInitializer( + IRCode code, DexClass clazz, NonTrivialInstanceInitializerInfo.Builder builder) { if (clazz.definesFinalizer(options.itemFactory)) { // Defining a finalize method can observe the side-effect of Object.<init> GC registration. return null; } - NonTrivialInstanceInitializerInfo.Builder builder = NonTrivialInstanceInitializerInfo.builder(); Value receiver = code.getThis(); boolean hasCatchHandler = false; for (BasicBlock block : code.blocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java index b65a579..46cff7b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -9,9 +9,11 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.ir.conversion.FieldOptimizationFeedback; import com.android.tools.r8.ir.conversion.MethodOptimizationFeedback; +import com.android.tools.r8.shaking.AppInfoWithLivenessModifier; import com.android.tools.r8.utils.ThreadUtils; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; public abstract class OptimizationFeedback implements FieldOptimizationFeedback, MethodOptimizationFeedback { @@ -34,4 +36,8 @@ }, executorService); } + + public void modifyAppInfoWithLiveness(Consumer<AppInfoWithLivenessModifier> consumer) { + // Intentionally empty. + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java index 0fec1f7..c241c0a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.AppInfoWithLivenessModifier; import com.android.tools.r8.utils.IteratorUtils; import com.android.tools.r8.utils.StringUtils; import java.util.BitSet; @@ -23,10 +24,13 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; public class OptimizationFeedbackDelayed extends OptimizationFeedback { // Caching of updated optimization info and processed status. + private final AppInfoWithLivenessModifier appInfoWithLivenessModifier = + AppInfoWithLiveness.modifier(); private final Map<DexEncodedField, MutableFieldOptimizationInfo> fieldOptimizationInfos = new IdentityHashMap<>(); private final Map<DexEncodedMethod, UpdatableMethodOptimizationInfo> methodOptimizationInfos = @@ -63,6 +67,15 @@ super.fixupOptimizationInfos(appView, executorService, fixer); } + @Override + public void modifyAppInfoWithLiveness(Consumer<AppInfoWithLivenessModifier> consumer) { + consumer.accept(appInfoWithLivenessModifier); + } + + public void refineAppInfoWithLiveness(AppInfoWithLiveness appInfo) { + appInfoWithLivenessModifier.modify(appInfo); + } + public void updateVisibleOptimizationInfo() { // Remove methods that have become obsolete. A method may become obsolete, for example, as a // result of the class staticizer, which aims to transform virtual methods on companion classes @@ -85,6 +98,7 @@ } public boolean noUpdatesLeft() { + assert appInfoWithLivenessModifier.isEmpty(); assert fieldOptimizationInfos.isEmpty() : StringUtils.join(fieldOptimizationInfos.keySet(), ", "); assert methodOptimizationInfos.isEmpty()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java new file mode 100644 index 0000000..54a50fe --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
@@ -0,0 +1,34 @@ +// 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.info.field; + +import com.android.tools.r8.graph.DexEncodedField; + +/** + * Represents that no information is known about the way a constructor initializes the instance + * fields of the newly created instance. + */ +public class EmptyInstanceFieldInitializationInfoCollection + extends InstanceFieldInitializationInfoCollection { + + private static final EmptyInstanceFieldInitializationInfoCollection INSTANCE = + new EmptyInstanceFieldInitializationInfoCollection(); + + private EmptyInstanceFieldInitializationInfoCollection() {} + + public static EmptyInstanceFieldInitializationInfoCollection getInstance() { + return INSTANCE; + } + + @Override + public InstanceFieldInitializationInfo get(DexEncodedField field) { + return UnknownInstanceFieldInitializationInfo.getInstance(); + } + + @Override + public boolean isEmpty() { + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java new file mode 100644 index 0000000..c56c562 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
@@ -0,0 +1,33 @@ +// 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.info.field; + +/** + * Used to represent that a constructor initializes an instance field on the newly created instance + * with argument number {@link #argumentIndex} from the constructor's argument list. + */ +public class InstanceFieldArgumentInitializationInfo extends InstanceFieldInitializationInfo { + + private final int argumentIndex; + + /** Intentionally package private, use {@link InstanceFieldInitializationInfoFactory} instead. */ + InstanceFieldArgumentInitializationInfo(int argumentIndex) { + this.argumentIndex = argumentIndex; + } + + public int getArgumentIndex() { + return argumentIndex; + } + + @Override + public boolean isArgumentInitializationInfo() { + return true; + } + + @Override + public InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() { + return this; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java new file mode 100644 index 0000000..312d7a0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
@@ -0,0 +1,27 @@ +// 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.info.field; + +/** + * Information about the way a constructor initializes an instance field on the newly created + * instance. + * + * <p>For example, this can be used to represent that a constructor always initializes a particular + * instance field with a constant, or with an argument from the constructor's argument list. + */ +public abstract class InstanceFieldInitializationInfo { + + public boolean isArgumentInitializationInfo() { + return false; + } + + public InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() { + return null; + } + + public boolean isUnknown() { + return false; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java new file mode 100644 index 0000000..4912d46 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -0,0 +1,46 @@ +// 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.info.field; + +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexField; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * A mapping from instance fields of a class to information about how a particular constructor + * initializes these instance fields. + * + * <p>Returns {@link UnknownInstanceFieldInitializationInfo} if no information is known about the + * initialization of a given instance field. + */ +public abstract class InstanceFieldInitializationInfoCollection { + + public static Builder builder() { + return new Builder(); + } + + public abstract InstanceFieldInitializationInfo get(DexEncodedField field); + + public abstract boolean isEmpty(); + + public static class Builder { + + Map<DexField, InstanceFieldInitializationInfo> infos = new IdentityHashMap<>(); + + public void recordInitializationInfo( + DexEncodedField field, InstanceFieldInitializationInfo info) { + assert !infos.containsKey(field.field); + infos.put(field.field, info); + } + + public InstanceFieldInitializationInfoCollection build() { + if (infos.isEmpty()) { + return EmptyInstanceFieldInitializationInfoCollection.getInstance(); + } + return new NonTrivialInstanceFieldInitializationInfoCollection(infos); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java new file mode 100644 index 0000000..42209b4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
@@ -0,0 +1,19 @@ +// 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.info.field; + +import java.util.concurrent.ConcurrentHashMap; + +public class InstanceFieldInitializationInfoFactory { + + private ConcurrentHashMap<Integer, InstanceFieldArgumentInitializationInfo> + argumentInitializationInfos = new ConcurrentHashMap<>(); + + public InstanceFieldArgumentInitializationInfo createArgumentInitializationInfo( + int argumentIndex) { + return argumentInitializationInfos.computeIfAbsent( + argumentIndex, InstanceFieldArgumentInitializationInfo::new); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java new file mode 100644 index 0000000..bdaaf78 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -0,0 +1,33 @@ +// 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.info.field; + +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexField; +import java.util.Map; + +/** See {@link InstanceFieldArgumentInitializationInfo}. */ +public class NonTrivialInstanceFieldInitializationInfoCollection + extends InstanceFieldInitializationInfoCollection { + + private final Map<DexField, InstanceFieldInitializationInfo> infos; + + NonTrivialInstanceFieldInitializationInfoCollection( + Map<DexField, InstanceFieldInitializationInfo> infos) { + assert !infos.isEmpty(); + assert infos.values().stream().noneMatch(InstanceFieldInitializationInfo::isUnknown); + this.infos = infos; + } + + @Override + public InstanceFieldInitializationInfo get(DexEncodedField field) { + return infos.getOrDefault(field.field, UnknownInstanceFieldInitializationInfo.getInstance()); + } + + @Override + public boolean isEmpty() { + return false; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java new file mode 100644 index 0000000..66e882e --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
@@ -0,0 +1,26 @@ +// 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.info.field; + +/** + * Represents that no information is known about the way a particular constructor initializes an + * instance field of the newly created instance. + */ +public class UnknownInstanceFieldInitializationInfo extends InstanceFieldInitializationInfo { + + private static final UnknownInstanceFieldInitializationInfo INSTANCE = + new UnknownInstanceFieldInitializationInfo(); + + private UnknownInstanceFieldInitializationInfo() {} + + public static UnknownInstanceFieldInitializationInfo getInstance() { + return INSTANCE; + } + + @Override + public boolean isUnknown() { + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java index ef3ca08..20aff53 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
@@ -7,6 +7,8 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet; +import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; public class DefaultInstanceInitializerInfo extends InstanceInitializerInfo { @@ -30,6 +32,11 @@ } @Override + public InstanceFieldInitializationInfoCollection fieldInitializationInfos() { + return EmptyInstanceFieldInitializationInfoCollection.getInstance(); + } + + @Override public AbstractFieldSet readSet() { return UnknownFieldSet.getInstance(); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java index 5452bce..b27888e 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
@@ -6,11 +6,14 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; public abstract class InstanceInitializerInfo { public abstract DexMethod getParent(); + public abstract InstanceFieldInitializationInfoCollection fieldInitializationInfos(); + /** * Returns an abstraction of the set of fields that may be as a result of executing this * initializer.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java index 70017cc..4e19cef 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet; +import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; public final class NonTrivialInstanceInitializerInfo extends InstanceInitializerInfo { @@ -18,12 +19,18 @@ private static final int RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN = 1 << 2; private final int data; + private final InstanceFieldInitializationInfoCollection fieldInitializationInfos; private final AbstractFieldSet readSet; private final DexMethod parent; - private NonTrivialInstanceInitializerInfo(int data, AbstractFieldSet readSet, DexMethod parent) { + private NonTrivialInstanceInitializerInfo( + int data, + InstanceFieldInitializationInfoCollection fieldInitializationInfos, + AbstractFieldSet readSet, + DexMethod parent) { assert verifyNoUnknownBits(data); this.data = data; + this.fieldInitializationInfos = fieldInitializationInfos; this.readSet = readSet; this.parent = parent; } @@ -37,8 +44,9 @@ return true; } - public static Builder builder() { - return new Builder(); + public static Builder builder( + InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) { + return new Builder(instanceFieldInitializationInfos); } @Override @@ -47,6 +55,11 @@ } @Override + public InstanceFieldInitializationInfoCollection fieldInitializationInfos() { + return fieldInitializationInfos; + } + + @Override public AbstractFieldSet readSet() { return readSet; } @@ -68,6 +81,8 @@ public static class Builder { + private final InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos; + private int data = INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT | NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS @@ -75,8 +90,15 @@ private AbstractFieldSet readSet = EmptyFieldSet.getInstance(); private DexMethod parent; + public Builder(InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) { + this.instanceFieldInitializationInfos = instanceFieldInitializationInfos; + } + private boolean isTrivial() { - return data == 0 && readSet.isTop() && parent == null; + return instanceFieldInitializationInfos.isEmpty() + && data == 0 + && readSet.isTop() + && parent == null; } public Builder markFieldAsRead(DexEncodedField field) { @@ -158,7 +180,8 @@ public InstanceInitializerInfo build() { return isTrivial() ? DefaultInstanceInitializerInfo.getInstance() - : new NonTrivialInstanceInitializerInfo(data, readSet, parent); + : new NonTrivialInstanceInitializerInfo( + data, instanceFieldInitializationInfos, readSet, parent); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java index 98e83c4..ed47e54 100644 --- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -156,11 +156,8 @@ @Override public final void buildPrelude(IRBuilder builder) { - DexSourceCode.buildArgumentsWithUnusedArgumentStubs( - builder, - 0, - builder.getMethod(), - DexSourceCode::doNothingWriteConsumer); + builder.buildArgumentsWithUnusedArgumentStubs( + 0, builder.getMethod(), DexSourceCode::doNothingWriteConsumer); } @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java index 48f29c2..6085350 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -42,9 +42,7 @@ } @Override - void processMetadata() { - assert !isProcessed; - isProcessed = true; + void processMetadata(KotlinClassMetadata.Class metadata) { kmClass = metadata.toKmClass(); } @@ -142,4 +140,8 @@ return this; } + @Override + public String toString() { + return clazz.toString() + ": " + kmClass.toString(); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java index 07ec533..4433017 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,15 +4,26 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.StringUtils; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; import kotlinx.metadata.jvm.KotlinClassHeader; import kotlinx.metadata.jvm.KotlinClassMetadata; public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> { + // TODO(b/70169921): is it better to maintain List<DexType>? + List<String> partClassNames; + static KotlinClassFacade fromKotlinClassMetadata( KotlinClassMetadata kotlinClassMetadata, DexClass clazz) { assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade; @@ -26,24 +37,32 @@ } @Override - void processMetadata() { - assert !isProcessed; - isProcessed = true; - // No API to explore metadata details, hence nothing to do further. + void processMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) { + // Part Class names are stored in `d1`, which is immutable. Make a copy instead. + partClassNames = new ArrayList<>(metadata.getPartClassNames()); + // No API to explore metadata details, hence nothing further to do. } @Override void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) { - // TODO(b/70169921): no idea yet! - assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type - || appView.options().enableKotlinMetadataRewritingForRenamedClasses - : toString(); + ListIterator<String> partClassIterator = partClassNames.listIterator(); + while (partClassIterator.hasNext()) { + String partClassName = partClassIterator.next(); + partClassIterator.remove(); + DexType partClassType = appView.dexItemFactory().createType( + DescriptorUtils.getDescriptorFromClassBinaryName(partClassName)); + String renamedPartClassName = toRenamedBinaryName(partClassType, appView, lens); + if (renamedPartClassName != null) { + partClassIterator.add(renamedPartClassName); + } + } } @Override KotlinClassHeader createHeader() { - // TODO(b/70169921): may need to update if `rewrite` is implemented. - return metadata.getHeader(); + KotlinClassMetadata.MultiFileClassFacade.Writer writer = + new KotlinClassMetadata.MultiFileClassFacade.Writer(); + return writer.write(partClassNames).getHeader(); } @Override @@ -61,4 +80,9 @@ return this; } + @Override + public String toString() { + return clazz.toString() + + ": MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")"; + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java index 58c5aff..f108252 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,10 +4,14 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.DescriptorUtils; import kotlinx.metadata.KmPackage; import kotlinx.metadata.jvm.KotlinClassHeader; import kotlinx.metadata.jvm.KotlinClassMetadata; @@ -15,6 +19,8 @@ public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> { KmPackage kmPackage; + // TODO(b/70169921): is it better to maintain DexType? + String facadeClassName; static KotlinClassPart fromKotlinClassMetadata( KotlinClassMetadata kotlinClassMetadata, DexClass clazz) { @@ -29,14 +35,16 @@ } @Override - void processMetadata() { - assert !isProcessed; - isProcessed = true; + void processMetadata(KotlinClassMetadata.MultiFileClassPart metadata) { kmPackage = metadata.toKmPackage(); + facadeClassName = metadata.getFacadeClassName(); } @Override void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) { + DexType facadeClassType = appView.dexItemFactory().createType( + DescriptorUtils.getDescriptorFromClassBinaryName(facadeClassName)); + facadeClassName = toRenamedBinaryName(facadeClassType, appView, lens); if (!appView.options().enableKotlinMetadataRewritingForMembers) { return; } @@ -45,10 +53,17 @@ @Override KotlinClassHeader createHeader() { - KotlinClassMetadata.MultiFileClassPart.Writer writer = - new KotlinClassMetadata.MultiFileClassPart.Writer(); - kmPackage.accept(writer); - return writer.write(metadata.getFacadeClassName()).getHeader(); + if (facadeClassName != null) { + KotlinClassMetadata.MultiFileClassPart.Writer writer = + new KotlinClassMetadata.MultiFileClassPart.Writer(); + kmPackage.accept(writer); + return writer.write(facadeClassName).getHeader(); + } else { + // It's no longer part of multi-file class. + KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer(); + kmPackage.accept(writer); + return writer.write().getHeader(); + } } @Override @@ -66,4 +81,9 @@ return this; } + @Override + public String toString() { + return clazz.toString() + ": " + kmPackage.toString() + + ": MultiFileClassPart(" + facadeClassName + ")"; + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java index 5eac335..bb4dc47 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -29,9 +29,7 @@ } @Override - void processMetadata() { - assert !isProcessed; - isProcessed = true; + void processMetadata(KotlinClassMetadata.FileFacade metadata) { kmPackage = metadata.toKmPackage(); } @@ -65,4 +63,8 @@ return this; } + @Override + public String toString() { + return clazz.toString() + ": " + kmPackage.toString(); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java index 56d9359..e9c1eb6 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -25,19 +25,16 @@ // Provides access to package/class-level Kotlin information. public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> { - final MetadataKind metadata; final DexClass clazz; - boolean isProcessed; KotlinInfo(MetadataKind metadata, DexClass clazz) { assert clazz != null; - this.metadata = metadata; this.clazz = clazz; - processMetadata(); + processMetadata(metadata); } // Subtypes will define how to process the given metadata. - abstract void processMetadata(); + abstract void processMetadata(MetadataKind metadata); // Subtypes will define how to rewrite metadata after shrinking and minification. // Subtypes that represent subtypes of {@link KmDeclarationContainer} can use @@ -96,12 +93,6 @@ return isClass() || isFile() || isClassPart(); } - @Override - public String toString() { - return (clazz != null ? clazz.toSourceString() : "<null class?!>") - + ": " + metadata.toString(); - } - // {@link KmClass} and {@link KmPackage} are inherited from {@link KmDeclarationContainer} that // abstract functions and properties. Rewriting of those portions can be unified here. void rewriteDeclarationContainer(
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 6e959b6..b16d90a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -5,6 +5,7 @@ import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature; import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature; +import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor; import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType; import static com.android.tools.r8.kotlin.Kotlin.NAME; import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier; @@ -44,6 +45,30 @@ return kmType; } + static DexType toRenamedType( + DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) { + // For library or classpath class, synthesize @Metadata always. + // For a program class, make sure it is live. + if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) { + return null; + } + DexType renamedType = lens.lookupType(type, appView.dexItemFactory()); + // For library or classpath class, we should not have renamed it. + DexClass clazz = appView.definitionFor(type); + assert clazz == null || clazz.isProgramClass() || renamedType == type + : type.toSourceString() + " -> " + renamedType.toSourceString(); + return renamedType; + } + + static String toRenamedBinaryName( + DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) { + DexType renamedType = toRenamedType(type, appView, lens); + if (renamedType == null) { + return null; + } + return getBinaryNameFromDescriptor(renamedType.toDescriptorString()); + } + static String toRenamedClassifier( DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) { // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray @@ -56,16 +81,10 @@ if (type.isArrayType()) { return NAME + "/Array"; } - // For library or classpath class, synthesize @Metadata always. - // For a program class, make sure it is live. - if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) { + DexType renamedType = toRenamedType(type, appView, lens); + if (renamedType == null) { return null; } - DexType renamedType = lens.lookupType(type, appView.dexItemFactory()); - // For library or classpath class, we should not have renamed it. - DexClass clazz = appView.definitionFor(type); - assert clazz == null || clazz.isProgramClass() || renamedType == type - : type.toSourceString() + " -> " + renamedType.toSourceString(); return descriptorToKotlinClassifier(renamedType.toDescriptorString()); } @@ -125,7 +144,7 @@ ? method.getKotlinMemberInfo().flag : method.accessFlags.getAsKotlinFlags(); KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString()); - JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(method.method)); + JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod)); KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens); if (kmReturnType == null) { return null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java index 28a6eb1..5cc7d2e 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -12,6 +12,9 @@ import kotlinx.metadata.jvm.KotlinClassMetadata; public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> { + // TODO(b/70169921): Once converted to internal data structure, this can be gone. + private KotlinClassMetadata.SyntheticClass metadata; + public enum Flavour { KotlinStyleLambda, JavaStyleLambda, @@ -41,9 +44,8 @@ } @Override - void processMetadata() { - assert !isProcessed; - isProcessed = true; + void processMetadata(KotlinClassMetadata.SyntheticClass metadata) { + this.metadata = metadata; if (metadata.isLambda()) { // TODO(b/70169921): Use #toKmLambda to store a mutable model if needed. } @@ -116,4 +118,8 @@ && clazz.interfaces.size() == 1; } + @Override + public String toString() { + return clazz.toString() + ": " + metadata.toString(); + } }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java index 1c75720..96cd284 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; @@ -32,10 +31,11 @@ import com.google.common.collect.Sets; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.function.Predicate; class ClassNameMinifier { @@ -91,11 +91,14 @@ } } - ClassRenaming computeRenaming(Timing timing) { - return computeRenaming(timing, Collections.emptyMap()); + ClassRenaming computeRenaming(Timing timing, ExecutorService executorService) + throws ExecutionException { + return computeRenaming(timing, executorService, Collections.emptyMap()); } - ClassRenaming computeRenaming(Timing timing, Map<DexType, DexString> syntheticClasses) { + ClassRenaming computeRenaming( + Timing timing, ExecutorService executorService, Map<DexType, DexString> syntheticClasses) + throws ExecutionException { // Externally defined synthetic classes populate an initial renaming. renaming.putAll(syntheticClasses); @@ -139,13 +142,8 @@ timing.begin("rename-generic"); new GenericSignatureRewriter(appView, renaming) .run( - new Iterable<DexProgramClass>() { - @Override - public Iterator<DexProgramClass> iterator() { - return IteratorUtils.<DexClass, DexProgramClass>filter( - classes.iterator(), DexClass::isProgramClass); - } - }); + () -> IteratorUtils.filter(classes.iterator(), DexClass::isProgramClass), + executorService); timing.end(); timing.begin("rename-arrays");
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java index 359de49..f2d7c1a 100644 --- a/src/main/java/com/android/tools/r8/naming/Minifier.java +++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -57,7 +57,7 @@ new MinificationPackageNamingStrategy(appView), // Use deterministic class order to make sure renaming is deterministic. appView.appInfo().classesWithDeterministicOrder()); - ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing); + ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing, executorService); timing.end(); assert new MinifiedRenaming(
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java index 7b6d191..471d66f 100644 --- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -137,7 +137,7 @@ new MinificationPackageNamingStrategy(appView), mappedClasses); ClassRenaming classRenaming = - classNameMinifier.computeRenaming(timing, syntheticCompanionClasses); + classNameMinifier.computeRenaming(timing, executorService, syntheticCompanionClasses); timing.end(); ApplyMappingMemberNamingStrategy nameStrategy =
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java index e891f95..b4ddb8e 100644 --- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java +++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -19,9 +19,12 @@ import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; +import com.android.tools.r8.utils.ThreadUtils; import com.google.common.collect.Maps; import java.lang.reflect.GenericSignatureFormatError; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; @@ -44,39 +47,44 @@ this.reporter = appView.options().reporter; } - public void run(Iterable<? extends DexProgramClass> classes) { - final GenericSignatureCollector genericSignatureCollector = new GenericSignatureCollector(); - final GenericSignatureParser<DexType> genericSignatureParser = - new GenericSignatureParser<>(genericSignatureCollector); + public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService) + throws ExecutionException { // Classes may not be the same as appInfo().classes() if applymapping is used on classpath // arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is // either ProgramClass or has a mapping. This is then transitively called inside the // ClassNameMinifier. - for (DexProgramClass clazz : classes) { - genericSignatureCollector.setCurrentClassContext(clazz); - clazz.setAnnotations( - rewriteGenericSignatures( - clazz.annotations(), - genericSignatureParser::parseClassSignature, - genericSignatureCollector::getRenamedSignature, - (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e))); - clazz.forEachField( - field -> - field.setAnnotations( - rewriteGenericSignatures( - field.annotations(), - genericSignatureParser::parseFieldSignature, - genericSignatureCollector::getRenamedSignature, - (signature, e) -> parseError(field, clazz.getOrigin(), signature, e)))); - clazz.forEachMethod( - method -> - method.setAnnotations( - rewriteGenericSignatures( - method.annotations(), - genericSignatureParser::parseMethodSignature, - genericSignatureCollector::getRenamedSignature, - (signature, e) -> parseError(method, clazz.getOrigin(), signature, e)))); - } + ThreadUtils.processItems( + classes, + clazz -> { + GenericSignatureCollector genericSignatureCollector = + new GenericSignatureCollector(clazz); + GenericSignatureParser<DexType> genericSignatureParser = + new GenericSignatureParser<>(genericSignatureCollector); + clazz.setAnnotations( + rewriteGenericSignatures( + clazz.annotations(), + genericSignatureParser::parseClassSignature, + genericSignatureCollector::getRenamedSignature, + (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e))); + clazz.forEachField( + field -> + field.setAnnotations( + rewriteGenericSignatures( + field.annotations(), + genericSignatureParser::parseFieldSignature, + genericSignatureCollector::getRenamedSignature, + (signature, e) -> parseError(field, clazz.getOrigin(), signature, e)))); + clazz.forEachMethod( + method -> + method.setAnnotations( + rewriteGenericSignatures( + method.annotations(), + genericSignatureParser::parseMethodSignature, + genericSignatureCollector::getRenamedSignature, + (signature, e) -> parseError(method, clazz.getOrigin(), signature, e)))); + }, + executorService + ); } private DexAnnotationSet rewriteGenericSignatures( @@ -154,17 +162,17 @@ private class GenericSignatureCollector implements GenericSignatureAction<DexType> { private StringBuilder renamedSignature; - private DexProgramClass currentClassContext; + private final DexProgramClass currentClassContext; private DexType lastWrittenType = null; + GenericSignatureCollector(DexProgramClass clazz) { + this.currentClassContext = clazz; + } + String getRenamedSignature() { return renamedSignature.toString(); } - void setCurrentClassContext(DexProgramClass clazz) { - currentClassContext = clazz; - } - @Override public void parsedSymbol(char symbol) { if (symbol == ';' && lastWrittenType == null) {
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 69ed3b0..509a832 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -504,6 +504,10 @@ private boolean dontAssertDefinitionFor = true; + public static AppInfoWithLivenessModifier modifier() { + return new AppInfoWithLivenessModifier(); + } + @Override public void enableDefinitionForAssert() { dontAssertDefinitionFor = false; @@ -715,11 +719,19 @@ return fieldAccessInfoCollection; } + FieldAccessInfoCollectionImpl getMutableFieldAccessInfoCollection() { + return fieldAccessInfoCollection; + } + /** This method provides immutable access to `objectAllocationInfoCollection`. */ public ObjectAllocationInfoCollection getObjectAllocationInfoCollection() { return objectAllocationInfoCollection; } + ObjectAllocationInfoCollectionImpl getMutableObjectAllocationInfoCollection() { + return objectAllocationInfoCollection; + } + private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) { Set<DexType> typeSet = ImmutableSet.copyOf(types); for (DexReference item : items) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java new file mode 100644 index 0000000..75c27fd --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
@@ -0,0 +1,58 @@ +// 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.shaking; + +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl; +import com.android.tools.r8.graph.FieldAccessInfoImpl; +import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl; +import com.google.common.collect.Sets; +import java.util.Set; + +/** Used to mutate AppInfoWithLiveness between waves. */ +public class AppInfoWithLivenessModifier { + + private final Set<DexProgramClass> noLongerInstantiatedClasses = Sets.newConcurrentHashSet(); + private final Set<DexField> noLongerWrittenFields = Sets.newConcurrentHashSet(); + + AppInfoWithLivenessModifier() {} + + public boolean isEmpty() { + return noLongerInstantiatedClasses.isEmpty(); + } + + public void removeInstantiatedType(DexProgramClass clazz) { + noLongerInstantiatedClasses.add(clazz); + } + + public void removeWrittenField(DexField field) { + noLongerWrittenFields.add(field); + } + + public void modify(AppInfoWithLiveness appInfo) { + // Instantiated classes. + ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection = + appInfo.getMutableObjectAllocationInfoCollection(); + noLongerInstantiatedClasses.forEach(objectAllocationInfoCollection::markNoLongerInstantiated); + + // Written fields. + FieldAccessInfoCollectionImpl fieldAccessInfoCollection = + appInfo.getMutableFieldAccessInfoCollection(); + noLongerWrittenFields.forEach( + field -> { + FieldAccessInfoImpl fieldAccessInfo = fieldAccessInfoCollection.get(field); + if (fieldAccessInfo != null) { + fieldAccessInfo.clearWrites(); + } + }); + + clear(); + } + + private void clear() { + noLongerInstantiatedClasses.clear(); + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java index f05d068..dfcee69 100644 --- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java +++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -32,11 +32,11 @@ } public DexProgramClass getContextHolder() { - return context.holder; + return context.getHolder(); } public DexEncodedMethod getContextMethod() { - return context.method; + return context.getMethod(); } @Override @@ -66,12 +66,12 @@ @Override public boolean registerInstanceFieldWrite(DexField field) { - return enqueuer.traceInstanceFieldWrite(field, context.method); + return enqueuer.traceInstanceFieldWrite(field, context.getMethod()); } @Override public boolean registerInstanceFieldRead(DexField field) { - return enqueuer.traceInstanceFieldRead(field, context.method); + return enqueuer.traceInstanceFieldRead(field, context.getMethod()); } @Override @@ -81,33 +81,33 @@ @Override public boolean registerStaticFieldRead(DexField field) { - return enqueuer.traceStaticFieldRead(field, context.method); + return enqueuer.traceStaticFieldRead(field, context.getMethod()); } @Override public boolean registerStaticFieldWrite(DexField field) { - return enqueuer.traceStaticFieldWrite(field, context.method); + return enqueuer.traceStaticFieldWrite(field, context.getMethod()); } @Override public boolean registerConstClass(DexType type) { - return enqueuer.traceConstClass(type, context.method); + return enqueuer.traceConstClass(type, context.getMethod()); } @Override public boolean registerCheckCast(DexType type) { - return enqueuer.traceCheckCast(type, context.method); + return enqueuer.traceCheckCast(type, context.getMethod()); } @Override public boolean registerTypeReference(DexType type) { - return enqueuer.traceTypeReference(type, context.method); + return enqueuer.traceTypeReference(type, context.getMethod()); } @Override public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) { super.registerMethodHandle(methodHandle, use); - enqueuer.traceMethodHandle(methodHandle, use, context.method); + enqueuer.traceMethodHandle(methodHandle, use, context.getMethod()); } @Override
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 d0e971e..a7bb64b 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -27,7 +27,6 @@ import com.android.tools.r8.graph.AppInfoWithSubtyping; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; -import com.android.tools.r8.graph.Descriptor; import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexAnnotationSet; import com.android.tools.r8.graph.DexCallSite; @@ -41,6 +40,7 @@ import com.android.tools.r8.graph.DexItem; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexLibraryClass; +import com.android.tools.r8.graph.DexMember; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexMethodHandle; import com.android.tools.r8.graph.DexProgramClass; @@ -106,6 +106,7 @@ import java.lang.reflect.InvocationHandler; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.HashMap; @@ -171,6 +172,9 @@ private ProguardClassFilter dontWarnPatterns; private final EnqueuerUseRegistryFactory useRegistryFactory; + private final Map<DexProgramClass, Set<DexProgramClass>> immediateSubtypesOfLiveTypes = + new IdentityHashMap<>(); + private final Map<DexMethod, Set<DexEncodedMethod>> virtualInvokes = new IdentityHashMap<>(); private final Map<DexMethod, Set<DexEncodedMethod>> interfaceInvokes = new IdentityHashMap<>(); private final Map<DexMethod, Set<DexEncodedMethod>> superInvokes = new IdentityHashMap<>(); @@ -365,9 +369,8 @@ instantiatedInterfaceTypes = Sets.newIdentityHashSet(); lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null; - // TODO(b/147799448): Enable allocation site tracking during the initial round of tree shaking. objectAllocationInfoCollection = - ObjectAllocationInfoCollectionImpl.builder(false, graphReporter); + ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter); if (appView.rewritePrefix.isRewriting() && mode.isInitialTreeShaking()) { desugaredLibraryWrapperAnalysis = new DesugaredLibraryConversionWrapperAnalysis(appView); @@ -636,13 +639,14 @@ bootstrapMethods.add(callSite.bootstrapMethod.asMethod()); } - LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, context.holder); + DexProgramClass contextHolder = context.getHolder(); + LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, contextHolder); if (descriptor == null) { if (!appInfo.isStringConcat(callSite.bootstrapMethod)) { if (options.reporter != null) { Diagnostic message = new StringDiagnostic( - "Unknown bootstrap method " + callSite.bootstrapMethod, context.holder.origin); + "Unknown bootstrap method " + callSite.bootstrapMethod, contextHolder.origin); options.reporter.warning(message); } } @@ -651,22 +655,23 @@ return; } + DexEncodedMethod contextMethod = context.getMethod(); for (DexType lambdaInstantiatedInterface : descriptor.interfaces) { - markLambdaInstantiated(lambdaInstantiatedInterface, context.method); + markLambdaInstantiated(lambdaInstantiatedInterface, contextMethod); } if (lambdaRewriter != null) { - assert context.method.getCode().isCfCode() : "Unexpected input type with lambdas"; - CfCode code = context.method.getCode().asCfCode(); + assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas"; + CfCode code = contextMethod.getCode().asCfCode(); if (code != null) { LambdaClass lambdaClass = - lambdaRewriter.getOrCreateLambdaClass(descriptor, context.method.method.holder); + lambdaRewriter.getOrCreateLambdaClass(descriptor, contextMethod.method.holder); lambdaClasses.put(lambdaClass.type, lambdaClass); lambdaCallSites - .computeIfAbsent(context.method, k -> new IdentityHashMap<>()) + .computeIfAbsent(contextMethod, k -> new IdentityHashMap<>()) .put(callSite, lambdaClass); if (lambdaClass.descriptor.interfaces.contains(appView.dexItemFactory().serializableType)) { - classesWithSerializableLambdas.add(context.holder); + classesWithSerializableLambdas.add(contextHolder); } } if (descriptor.delegatesToLambdaImplMethod()) { @@ -794,8 +799,8 @@ } boolean traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) { - DexProgramClass currentHolder = context.holder; - DexEncodedMethod currentMethod = context.method; + DexProgramClass currentHolder = context.getHolder(); + DexEncodedMethod currentMethod = context.getMethod(); boolean skipTracing = registerDeferredActionForDeadProtoBuilder( invokedMethod.holder, @@ -827,12 +832,12 @@ boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, ProgramMethod context) { return traceInvokeDirect( - invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method)); + invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod())); } private boolean traceInvokeDirect( DexMethod invokedMethod, ProgramMethod context, KeepReason reason) { - DexEncodedMethod currentMethod = context.method; + DexEncodedMethod currentMethod = context.getMethod(); if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, currentMethod)) { return false; } @@ -845,18 +850,17 @@ } boolean traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) { - return traceInvokeInterface( - invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method)); + return traceInvokeInterface(invokedMethod, context, KeepReason.invokedFrom(context)); } boolean traceInvokeInterfaceFromLambda(DexMethod invokedMethod, ProgramMethod context) { return traceInvokeInterface( - invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method)); + invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod())); } private boolean traceInvokeInterface( DexMethod method, ProgramMethod context, KeepReason keepReason) { - DexEncodedMethod currentMethod = context.method; + DexEncodedMethod currentMethod = context.getMethod(); if (!registerMethodWithTargetAndContext(interfaceInvokes, method, currentMethod)) { return false; } @@ -869,18 +873,17 @@ } boolean traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) { - return traceInvokeStatic( - invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method)); + return traceInvokeStatic(invokedMethod, context, KeepReason.invokedFrom(context)); } boolean traceInvokeStaticFromLambda(DexMethod invokedMethod, ProgramMethod context) { return traceInvokeStatic( - invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method)); + invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod())); } private boolean traceInvokeStatic( DexMethod invokedMethod, ProgramMethod context, KeepReason reason) { - DexEncodedMethod currentMethod = context.method; + DexEncodedMethod currentMethod = context.getMethod(); DexItemFactory dexItemFactory = appView.dexItemFactory(); if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod) || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) { @@ -912,8 +915,7 @@ } boolean traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) { - DexProgramClass currentHolder = context.holder; - DexEncodedMethod currentMethod = context.method; + DexEncodedMethod currentMethod = context.getMethod(); // We have to revisit super invokes based on the context they are found in. The same // method descriptor will hit different targets, depending on the context it is used in. DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, currentMethod); @@ -929,27 +931,26 @@ } boolean traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) { - return traceInvokeVirtual( - invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method)); + return traceInvokeVirtual(invokedMethod, context, KeepReason.invokedFrom(context)); } boolean traceInvokeVirtualFromLambda(DexMethod invokedMethod, ProgramMethod context) { return traceInvokeVirtual( - invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method)); + invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod())); } private boolean traceInvokeVirtual( DexMethod invokedMethod, ProgramMethod context, KeepReason reason) { if (invokedMethod == appView.dexItemFactory().classMethods.newInstance || invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) { - pendingReflectiveUses.add(context.method); + pendingReflectiveUses.add(context.getMethod()); } else if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(invokedMethod)) { // Implicitly add -identifiernamestring rule for the Java reflection in use. identifierNameStrings.add(invokedMethod); // Revisit the current method to implicitly add -keep rule for items with reflective access. - pendingReflectiveUses.add(context.method); + pendingReflectiveUses.add(context.getMethod()); } - if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context.method)) { + if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context.getMethod())) { return false; } if (Log.ENABLED) { @@ -961,7 +962,7 @@ } boolean traceNewInstance(DexType type, ProgramMethod context) { - DexEncodedMethod currentMethod = context.method; + DexEncodedMethod currentMethod = context.getMethod(); boolean skipTracing = registerDeferredActionForDeadProtoBuilder( type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, context)); @@ -981,7 +982,7 @@ type, context, InstantiationReason.LAMBDA, - KeepReason.invokedFromLambdaCreatedIn(context.method)); + KeepReason.invokedFromLambdaCreatedIn(context.getMethod())); } private boolean traceNewInstance( @@ -989,7 +990,7 @@ ProgramMethod context, InstantiationReason instantiationReason, KeepReason keepReason) { - DexEncodedMethod currentMethod = context.method; + DexEncodedMethod currentMethod = context.getMethod(); DexProgramClass clazz = getProgramClassOrNull(type); if (clazz != null) { if (clazz.isAnnotation() || clazz.isInterface()) { @@ -1248,6 +1249,19 @@ witness); } + private void addImmediateSubtype(DexProgramClass superType, DexProgramClass subType) { + assert liveTypes.contains(subType); + assert subType.superType == superType.type + || Arrays.asList(subType.interfaces.values).contains(superType.type); + immediateSubtypesOfLiveTypes + .computeIfAbsent(superType, k -> Sets.newIdentityHashSet()) + .add(subType); + } + + private Set<DexProgramClass> getImmediateLiveSubtypes(DexProgramClass clazz) { + return immediateSubtypesOfLiveTypes.getOrDefault(clazz, Collections.emptySet()); + } + private void markTypeAsLive( DexProgramClass holder, ScopedDexMethodSet seen, KeepReasonWitness witness) { if (!liveTypes.add(holder, witness)) { @@ -1277,6 +1291,10 @@ holder.superType, ignore -> new ScopedDexMethodSet()); seen.setParent(seenForSuper); markTypeAsLive(holder.superType, reason); + DexProgramClass superClass = getProgramClassOrNull(holder.superType); + if (superClass != null) { + addImmediateSubtype(superClass, holder); + } } // If this is an interface that has just become live, then report previously seen but unreported @@ -1345,6 +1363,8 @@ return; } + addImmediateSubtype(clazz, implementer); + if (!appView.options().enableUnusedInterfaceRemoval || mode.isTracingMainDex()) { markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer)); } else { @@ -1388,10 +1408,7 @@ private void processAnnotation( DexProgramClass holder, DexDefinition annotatedItem, DexAnnotation annotation) { assert annotatedItem == holder - || (annotatedItem.isDexEncodedField() - && annotatedItem.asDexEncodedField().field.holder == holder.type) - || (annotatedItem.isDexEncodedMethod() - && annotatedItem.asDexEncodedMethod().method.holder == holder.type); + || annotatedItem.asDexEncodedMember().toReference().holder == holder.type; assert !holder.isDexClass() || holder.asDexClass().isProgramClass(); DexType type = annotation.annotation.type; recordTypeReference(type); @@ -2135,7 +2152,7 @@ if (contextOrNull != null && !resolution.isUnresolved() && !AccessControl.isMethodAccessible( - resolution.method, holder, contextOrNull.holder, appInfo)) { + resolution.method, holder, contextOrNull.getHolder(), appInfo)) { // Not accessible from this context, so this call will cause a runtime exception. // Note that the resolution is not cached, as another call context may be valid. return; @@ -2155,10 +2172,11 @@ assert resolution.holder.isProgramClass(); assert interfaceInvoke == holder.isInterface(); + DexProgramClass context = contextOrNull == null ? null : contextOrNull.getHolder(); LookupResult lookupResult = // TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved. new SingleResolutionResult(holder, resolution.holder, resolution.method) - .lookupVirtualDispatchTargets(appView, appInfo); + .lookupVirtualDispatchTargets(context, appView, appInfo); if (!lookupResult.isLookupResultSuccess()) { return; } @@ -2412,10 +2430,11 @@ assert fieldAccessInfoCollection.verifyMappingIsOneToOne(); for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) { - appView.appInfo().invalidateTypeCacheFor(bridge.holder.type); - bridge.holder.appendVirtualMethod(bridge.method); - targetedMethods.add(bridge.method, graphReporter.fakeReportShouldNotBeUsed()); - liveMethods.add(bridge.holder, bridge.method, graphReporter.fakeReportShouldNotBeUsed()); + appView.appInfo().invalidateTypeCacheFor(bridge.getHolder().type); + bridge.getHolder().appendVirtualMethod(bridge.getMethod()); + targetedMethods.add(bridge.getMethod(), graphReporter.fakeReportShouldNotBeUsed()); + liveMethods.add( + bridge.getHolder(), bridge.getMethod(), graphReporter.fakeReportShouldNotBeUsed()); } // Ensure references from various root set collections. @@ -2693,12 +2712,12 @@ assert replaced == callSites.size(); } - private static <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet( - Set<? extends DexEncodedMember<T>> set) { - ImmutableSortedSet.Builder<T> builder = + private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> + SortedSet<R> toSortedDescriptorSet(Set<D> set) { + ImmutableSortedSet.Builder<R> builder = new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompareTo); - for (DexEncodedMember<T> item : set) { - builder.add(item.getKey()); + for (D item : set) { + builder.add(item.toReference()); } return builder.build(); } @@ -2846,25 +2865,27 @@ InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) { ProgramMethod methodToKeep = action.getMethodToKeep(); ProgramMethod singleTarget = action.getSingleTarget(); - if (rootSet.noShrinking.containsKey(singleTarget.method.method)) { + DexEncodedMethod singleTargetMethod = singleTarget.getMethod(); + if (rootSet.noShrinking.containsKey(singleTargetMethod.method)) { return; } if (methodToKeep != singleTarget) { - assert null == methodToKeep.holder.lookupMethod(methodToKeep.method.method); + assert null == methodToKeep.getHolder().lookupMethod(methodToKeep.getMethod().method); ProgramMethod old = - syntheticInterfaceMethodBridges.put(methodToKeep.method.method, methodToKeep); + syntheticInterfaceMethodBridges.put(methodToKeep.getMethod().method, methodToKeep); if (old == null) { - if (singleTarget.method.isLibraryMethodOverride().isTrue()) { - methodToKeep.method.setLibraryMethodOverride(OptionalBool.TRUE); + if (singleTargetMethod.isLibraryMethodOverride().isTrue()) { + methodToKeep.getMethod().setLibraryMethodOverride(OptionalBool.TRUE); } - assert singleTarget.holder.isInterface(); + DexProgramClass singleTargetHolder = singleTarget.getHolder(); + assert singleTargetHolder.isInterface(); markVirtualMethodAsReachable( - singleTarget.method.method, - singleTarget.holder.isInterface(), + singleTargetMethod.method, + singleTargetHolder.isInterface(), null, graphReporter.fakeReportShouldNotBeUsed()); enqueueMarkMethodLiveAction( - singleTarget.holder, singleTarget.method, graphReporter.fakeReportShouldNotBeUsed()); + singleTargetHolder, singleTargetMethod, graphReporter.fakeReportShouldNotBeUsed()); } } action.getAction().accept(builder); @@ -2938,18 +2959,16 @@ // If there is a subtype of `clazz` that escapes into the library and does not override `method` // then we need to mark the method as being reachable. - Deque<DexType> worklist = new ArrayDeque<>(appView.appInfo().allImmediateSubtypes(clazz.type)); - - Set<DexType> visited = Sets.newIdentityHashSet(); - visited.addAll(worklist); + Set<DexProgramClass> immediateSubtypes = getImmediateLiveSubtypes(clazz); + if (immediateSubtypes.isEmpty()) { + return false; + } + Deque<DexProgramClass> worklist = new ArrayDeque<>(immediateSubtypes); + Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(immediateSubtypes); while (!worklist.isEmpty()) { - DexClass current = appView.definitionFor(worklist.removeFirst()); - if (current == null) { - continue; - } - - assert visited.contains(current.type); + DexProgramClass current = worklist.removeFirst(); + assert visited.contains(current); if (current.lookupVirtualMethod(method.method) != null) { continue; @@ -2959,7 +2978,7 @@ return true; } - for (DexType subtype : appView.appInfo().allImmediateSubtypes(current.type)) { + for (DexProgramClass subtype : getImmediateLiveSubtypes(current)) { if (visited.add(subtype)) { worklist.add(subtype); } @@ -3543,17 +3562,17 @@ } } - private static final class TargetWithContext<T extends Descriptor<?, T>> { + private static final class TargetWithContext<R extends DexMember<?, R>> { - private final T target; + private final R target; private final DexEncodedMethod context; - private TargetWithContext(T target, DexEncodedMethod context) { + private TargetWithContext(R target, DexEncodedMethod context) { this.target = target; this.context = context; } - public T getTarget() { + public R getTarget() { return target; }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java index 680c0b5..cc86522 100644 --- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java +++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramMethod; // TODO(herhut): Canonicalize reason objects. public abstract class KeepReason { @@ -40,6 +41,10 @@ return new InvokedFrom(holder, method); } + public static KeepReason invokedFrom(ProgramMethod context) { + return invokedFrom(context.getHolder(), context.getMethod()); + } + public static KeepReason invokedFromLambdaCreatedIn(DexEncodedMethod method) { return new InvokedFromLambdaCreatedIn(method); }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java index 7495c6b..7d93686 100644 --- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java +++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -557,9 +557,10 @@ resolutionResult.getResolvedHolder().asProgramClass(), resolutionResult.getResolvedMethod()); ProgramMethod methodToKeep = - canInsertForwardingMethod(originalClazz, resolutionMethod.method) + canInsertForwardingMethod(originalClazz, resolutionMethod.getMethod()) ? new ProgramMethod( - originalClazz, resolutionMethod.method.toForwardingMethod(originalClazz, appView)) + originalClazz, + resolutionMethod.getMethod().toForwardingMethod(originalClazz, appView)) : resolutionMethod; delayedRootSetActionItems.add( @@ -576,9 +577,9 @@ rule); } DexDefinition precondition = - testAndGetPrecondition(methodToKeep.method, preconditionSupplier); + testAndGetPrecondition(methodToKeep.getMethod(), preconditionSupplier); rootSetBuilder.addItemToSets( - methodToKeep.method, context, rule, precondition, ifRule); + methodToKeep.getMethod(), context, rule, precondition, ifRule); })); } } @@ -1539,7 +1540,7 @@ // Create a Set of the fields to avoid quadratic behavior. fields = Streams.stream(clazz.fields()) - .map(DexEncodedField::getKey) + .map(DexEncodedField::toReference) .collect(Collectors.toSet()); } assert fields.contains(requiredField) @@ -1552,7 +1553,7 @@ // Create a Set of the methods to avoid quadratic behavior. methods = Streams.stream(clazz.methods()) - .map(DexEncodedMethod::getKey) + .map(DexEncodedMethod::toReference) .collect(Collectors.toSet()); } assert methods.contains(requiredMethod)
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java index 3ded35c..05d281b 100644 --- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java +++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMember; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMember; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexReference; @@ -17,7 +18,6 @@ import com.android.tools.r8.graph.EnclosingMethodAttribute; import com.android.tools.r8.graph.InnerClassAttribute; import com.android.tools.r8.graph.NestMemberClassAttribute; -import com.android.tools.r8.graph.PresortedComparable; import com.android.tools.r8.logging.Log; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; @@ -243,8 +243,8 @@ return context == null || !isTypeLive(context); } - private <S extends PresortedComparable<S>, T extends DexEncodedMember<S>> - int firstUnreachableIndex(List<T> items, Predicate<T> live) { + private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> int firstUnreachableIndex( + List<D> items, Predicate<D> live) { for (int i = 0; i < items.size(); i++) { if (!live.test(items.get(i))) { return i; @@ -268,7 +268,7 @@ } for (int i = firstUnreachable; i < methods.size(); i++) { DexEncodedMethod method = methods.get(i); - if (appInfo.liveMethods.contains(method.getKey())) { + if (appInfo.liveMethods.contains(method.toReference())) { reachableMethods.add(method); } else if (options.configurationDebugging) { // Keep the method but rewrite its body, if it has one. @@ -277,7 +277,7 @@ ? method : method.toMethodThatLogsError(appView)); methodsToKeepForConfigurationDebugging.add(method.method); - } else if (appInfo.targetedMethods.contains(method.getKey())) { + } else if (appInfo.targetedMethods.contains(method.toReference())) { // If the method is already abstract, and doesn't have code, let it be. if (method.shouldNotHaveCode() && !method.hasCode()) { reachableMethods.add(method);
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index 76db9fe..3ba6b3a 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.DexEncodedMember; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMember; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexProto; @@ -30,7 +31,6 @@ import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; -import com.android.tools.r8.graph.PresortedComparable; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.graph.RewrittenPrototypeDescription; import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; @@ -335,7 +335,7 @@ } assert Streams.stream(Iterables.concat(clazz.fields(), clazz.methods())) - .map(DexEncodedMember::getKey) + .map(DexEncodedMember::toReference) .noneMatch(appInfo::isPinned); if (appView.options().featureSplitConfiguration != null && @@ -420,7 +420,8 @@ // We basically can't move the clinit, since it is not called when implementing classes have // their clinit called - except when the interface has a default method. if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer()) - || targetClass.classInitializationMayHaveSideEffects(appView, type -> type == clazz.type) + || targetClass.classInitializationMayHaveSideEffects( + appView, type -> type == clazz.type, Sets.newIdentityHashSet()) || (clazz.isInterface() && clazz.classInitializationMayHaveSideEffects(appView))) { // TODO(herhut): Handle class initializers. if (Log.ENABLED) { @@ -699,7 +700,7 @@ return true; } - private boolean methodResolutionMayChange(DexClass source, DexClass target) { + private boolean methodResolutionMayChange(DexProgramClass source, DexProgramClass target) { for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) { DexEncodedMethod directTargetMethod = target.lookupDirectMethod(virtualSourceMethod.method); if (directTargetMethod != null) { @@ -738,7 +739,7 @@ LookupResult lookupResult = appInfo .resolveMethodOnInterface(method.method.holder, method.method) - .lookupVirtualDispatchTargets(appView, appInfo); + .lookupVirtualDispatchTargets(target, appView); assert lookupResult.isLookupResultSuccess(); if (lookupResult.isLookupResultFailure()) { return true; @@ -917,10 +918,7 @@ add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get()); deferredRenamings.map(directMethod.method, resultingDirectMethod.method); deferredRenamings.recordMove(directMethod.method, resultingDirectMethod.method); - - if (!directMethod.isStatic()) { - blockRedirectionOfSuperCalls(resultingDirectMethod.method); - } + blockRedirectionOfSuperCalls(resultingDirectMethod.method); } } @@ -1269,15 +1267,15 @@ return null; } - private <T extends DexEncodedMember<S>, S extends PresortedComparable<S>> void add( - Map<Wrapper<S>, T> map, T item, Equivalence<S> equivalence) { - map.put(equivalence.wrap(item.getKey()), item); + private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void add( + Map<Wrapper<R>, D> map, D item, Equivalence<R> equivalence) { + map.put(equivalence.wrap(item.toReference()), item); } - private <T extends DexEncodedMember<S>, S extends PresortedComparable<S>> void addAll( - Collection<Wrapper<S>> collection, Iterable<T> items, Equivalence<S> equivalence) { - for (T item : items) { - collection.add(equivalence.wrap(item.getKey())); + private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void addAll( + Collection<Wrapper<R>> collection, Iterable<D> items, Equivalence<R> equivalence) { + for (D item : items) { + collection.add(equivalence.wrap(item.toReference())); } }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java index 78edccb..68f1749 100644 --- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java +++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -308,7 +308,8 @@ // methods, we either did not rename them, we renamed them according to a supplied map or // they may be bridges for interface methods with covariant return types. sortMethods(methods); - assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods); + // TODO(b/149360203): Reenable assert. + // assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods); } boolean identityMapping =
diff --git a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java index 40fa54f..2458a64 100644 --- a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java +++ b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
@@ -5,25 +5,25 @@ import com.android.tools.r8.errors.InternalCompilerError; import com.android.tools.r8.graph.DexEncodedMember; -import com.android.tools.r8.graph.PresortedComparable; +import com.android.tools.r8.graph.DexMember; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; -public class OrderedMergingIterator<T extends DexEncodedMember<S>, S extends PresortedComparable<S>> - implements Iterator<T> { +public class OrderedMergingIterator<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> + implements Iterator<D> { - private final List<T> one; - private final List<T> other; + private final List<D> one; + private final List<D> other; private int oneIndex = 0; private int otherIndex = 0; - public OrderedMergingIterator(List<T> one, List<T> other) { + public OrderedMergingIterator(List<D> one, List<D> other) { this.one = one; this.other = other; } - private static <T> T getNextChecked(List<T> list, int position) { + private D getNextChecked(List<D> list, int position) { if (position >= list.size()) { throw new NoSuchElementException(); } @@ -36,14 +36,14 @@ } @Override - public T next() { + public D next() { if (oneIndex >= one.size()) { return getNextChecked(other, otherIndex++); } if (otherIndex >= other.size()) { return getNextChecked(one, oneIndex++); } - int comparison = one.get(oneIndex).getKey().compareTo(other.get(otherIndex).getKey()); + int comparison = one.get(oneIndex).toReference().compareTo(other.get(otherIndex).toReference()); if (comparison < 0) { return one.get(oneIndex++); }
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java new file mode 100644 index 0000000..b631262 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -0,0 +1,49 @@ +// 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 com.google.common.collect.Sets; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Set; + +public class WorkList<T> { + + private final Deque<T> workingList = new ArrayDeque<>(); + private final Set<T> seen; + + public WorkList(EqualityTest equalityTest) { + if (equalityTest == EqualityTest.HASH) { + seen = new HashSet<>(); + } else { + seen = Sets.newIdentityHashSet(); + } + } + + public void addIfNotSeen(Iterable<T> items) { + items.forEach(this::addIfNotSeen); + } + + public void addIfNotSeen(T item) { + if (seen.add(item)) { + workingList.addLast(item); + } + } + + public boolean hasNext() { + return !workingList.isEmpty(); + } + + public T next() { + assert hasNext(); + return workingList.removeFirst(); + } + + public enum EqualityTest { + HASH, + IDENTITY + } +}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java index fbacef6..14d460e 100644 --- a/src/test/java/com/android/tools/r8/D8CommandTest.java +++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -142,6 +142,24 @@ handler).build())); } + @Test(expected = CompilationFailedException.class) + public void recursiveFlagsFile() throws Throwable { + Path working = temp.getRoot().toPath(); + Path flagsFile = working.resolve("flags.txt"); + Path recursiveFlagsFile = working.resolve("recursive_flags.txt"); + Path input = Paths.get(EXAMPLES_BUILD_DIR + "/arithmetic.jar").toAbsolutePath(); + FileUtils.writeTextFile(recursiveFlagsFile, "--output", "output.zip"); + FileUtils.writeTextFile( + flagsFile, "--min-api", "24", input.toString(), "@" + recursiveFlagsFile); + DiagnosticsChecker.checkErrorsContains( + "Recursive @argfiles are not supported", + handler -> + D8.run( + D8Command.parse( + new String[] {"@" + flagsFile.toString()}, EmbeddedOrigin.INSTANCE, handler) + .build())); + } + @Test public void printsHelpOnNoInput() throws Throwable { ProcessResult result = ToolHelper.forkD8(temp.getRoot().toPath());
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java index cc92bf3..4a703d7 100644 --- a/src/test/java/com/android/tools/r8/R8CommandTest.java +++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -143,7 +143,6 @@ assertEquals(Tool.R8, marker.getTool()); } - @Test(expected=CompilationFailedException.class) public void nonExistingFlagsFile() throws Throwable { Path working = temp.getRoot().toPath(); @@ -157,6 +156,24 @@ handler).build())); } + @Test(expected = CompilationFailedException.class) + public void recursiveFlagsFile() throws Throwable { + Path working = temp.getRoot().toPath(); + Path flagsFile = working.resolve("flags.txt"); + Path recursiveFlagsFile = working.resolve("recursive_flags.txt"); + Path input = Paths.get(EXAMPLES_BUILD_DIR + "/arithmetic.jar").toAbsolutePath(); + FileUtils.writeTextFile(recursiveFlagsFile, "--output", "output.zip"); + FileUtils.writeTextFile( + flagsFile, "--min-api", "24", input.toString(), "@" + recursiveFlagsFile); + DiagnosticsChecker.checkErrorsContains( + "Recursive @argfiles are not supported", + handler -> + R8.run( + R8Command.parse( + new String[] {"@" + flagsFile.toString()}, EmbeddedOrigin.INSTANCE, handler) + .build())); + } + @Test public void printsHelpOnNoInput() throws Throwable { ProcessResult result = ToolHelper.forkR8(temp.getRoot().toPath());
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java new file mode 100644 index 0000000..8dcad81 --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.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.classmerging; + +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.DescriptorUtils; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +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 VerticalClassMergerSuperCallInStaticTest extends TestBase { + + private static final String[] EXPECTED = new String[] {"A.collect()", "Base.collect()"}; + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public VerticalClassMergerSuperCallInStaticTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testRuntime() throws IOException, CompilationFailedException, ExecutionException { + testForRuntime(parameters) + .addProgramClasses(Base.class, B.class, Main.class) + .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testR8() throws IOException, CompilationFailedException, ExecutionException { + testForR8(parameters.getBackend()) + .addProgramClasses(Base.class, B.class, Main.class) + .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase()) + .addKeepMainRule(Main.class) + .addKeepClassRules(Base.class, B.class) + .enableInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + private byte[] getAWithRewrittenInvokeSpecialToBase() throws IOException { + return transformer(A.class) + .transformMethodInsnInMethod( + "callSuper", + (opcode, owner, name, descriptor, isInterface, continuation) -> { + continuation.apply( + INVOKESPECIAL, + DescriptorUtils.getBinaryNameFromJavaType(Base.class.getTypeName()), + name, + descriptor, + false); + }) + .transform(); + } + + public static class Base { + + public void collect() { + System.out.println("Base.collect()"); + } + } + + public static class A extends Base { + + @Override + @NeverInline + public void collect() { + System.out.println("A.collect()"); + } + + @NeverInline + public static void callSuper(A a) { + a.collect(); // Will be rewritten from invoke-virtual to invoke-special Base.collect(); + } + } + + public static class B extends A { + + public void bar() { + collect(); + } + } + + public static class Main { + + public static void main(String[] args) { + B b = new B(); + b.bar(); + A.callSuper(b); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java index 90993ee..6a1e059 100644 --- a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java +++ b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
@@ -48,6 +48,8 @@ Method m; m = A.class.getMethod("method0"); m.invoke(a); + // The call with in-exact argument to getMethod is intended and should stay. + // The warning cannot be suppressed according to b/117198454. m = A.class.getMethod("method0", null); m.invoke(a); m = A.class.getMethod("method0", (Class<?>[]) null);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java index af4088e..21074c9 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -6,7 +6,6 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; -import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java index ddcb725..a15dc08 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
@@ -10,7 +10,7 @@ import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -19,16 +19,22 @@ @RunWith(Parameterized.class) public class ComparisonEnumUnboxingAnalysisTest extends EnumUnboxingTestBase { - private final TestParameters parameters; - private final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class}; + private static final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class}; - @Parameters(name = "{0}") - public static TestParametersCollection data() { + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final boolean enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { return enumUnboxingTestParameters(); } - public ComparisonEnumUnboxingAnalysisTest(TestParameters parameters) { + public ComparisonEnumUnboxingAnalysisTest( + TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) { this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; } @Test @@ -37,9 +43,9 @@ testForR8(parameters.getBackend()) .addInnerClasses(ComparisonEnumUnboxingAnalysisTest.class) .addKeepMainRules(INPUTS) - .addKeepRules(KEEP_ENUM) .enableInliningAnnotations() - .addOptionsModification(this::enableEnumOptions) + .addKeepRules(enumKeepRules ? KEEP_ENUM : "") + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) .allowDiagnosticInfoMessages() .setMinApi(parameters.getApiLevel()) .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java index 332ad2e..b7ebc83 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -10,9 +10,9 @@ import com.android.tools.r8.Diagnostic; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestDiagnosticMessages; -import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.TestRuntime.CfVm; import com.android.tools.r8.ToolHelper.DexVm; +import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.StringUtils; import java.util.List; @@ -34,8 +34,9 @@ } } - void enableEnumOptions(InternalOptions options) { + void enableEnumOptions(InternalOptions options, boolean enumValueOptimization) { options.enableEnumUnboxing = true; + options.enableEnumValueOptimization = enumValueOptimization; options.testing.enableEnumUnboxingDebugLogs = true; } @@ -55,12 +56,15 @@ diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName())); } - static TestParametersCollection enumUnboxingTestParameters() { - return getTestParameters() - .withCfRuntime(CfVm.JDK9) - .withDexRuntime(DexVm.Version.first()) - .withDexRuntime(DexVm.Version.last()) - .withAllApiLevels() - .build(); + static List<Object[]> enumUnboxingTestParameters() { + return buildParameters( + getTestParameters() + .withCfRuntime(CfVm.JDK9) + .withDexRuntime(DexVm.Version.first()) + .withDexRuntime(DexVm.Version.last()) + .withAllApiLevels() + .build(), + BooleanUtils.values(), + BooleanUtils.values()); } }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java index 9c4872f..c0eeaf4 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
@@ -12,13 +12,13 @@ import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInstanceFieldMain.EnumInstanceField; import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInterfaceMain.EnumInterface; import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticFieldMain.EnumStaticField; import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticMethodMain.EnumStaticMethod; import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumVirtualMethodMain.EnumVirtualMethod; import com.android.tools.r8.utils.codeinspector.CodeInspector; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -36,14 +36,19 @@ }; private final TestParameters parameters; + private final boolean enumValueOptimization; + private final boolean enumKeepRules; - @Parameters(name = "{0}") - public static TestParametersCollection data() { + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { return enumUnboxingTestParameters(); } - public FailingEnumUnboxingAnalysisTest(TestParameters parameters) { + public FailingEnumUnboxingAnalysisTest( + TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) { this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; } @Test @@ -57,8 +62,8 @@ r8FullTestBuilder .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface. .enableInliningAnnotations() - .addKeepRules(KEEP_ENUM) - .addOptionsModification(this::enableEnumOptions) + .addKeepRules(enumKeepRules ? KEEP_ENUM : "") + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) .allowDiagnosticInfoMessages() .setMinApi(parameters.getApiLevel()) .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java index c7ad117..944c9c3 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -11,9 +11,9 @@ import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.codeinspector.CodeInspector; import java.util.EnumSet; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -31,14 +31,19 @@ }; private final TestParameters parameters; + private final boolean enumValueOptimization; + private final boolean enumKeepRules; - @Parameters(name = "{0}") - public static TestParametersCollection data() { + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { return enumUnboxingTestParameters(); } - public FailingMethodEnumUnboxingAnalysisTest(TestParameters parameters) { + public FailingMethodEnumUnboxingAnalysisTest( + TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) { this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; } @Test @@ -47,13 +52,10 @@ testForR8(parameters.getBackend()) .addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class) .addKeepMainRules(FAILURES) - .addKeepRules(KEEP_ENUM) - .addOptionsModification(this::enableEnumOptions) + .addKeepRules(enumKeepRules ? KEEP_ENUM : "") + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) .allowDiagnosticInfoMessages() .enableInliningAnnotations() - .addOptionsModification( - // Disabled to avoid toString() being removed. - opt -> opt.enableEnumValueOptimization = false) .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::assertEnumsAsExpected); @@ -64,9 +66,14 @@ m -> assertEnumIsBoxed( failure.getDeclaredClasses()[0], failure.getSimpleName(), m)) - .run(parameters.getRuntime(), failure) - .assertSuccess(); - assertLines2By2Correct(run.getStdOut()); + .run(parameters.getRuntime(), failure); + if (failure == EnumSetTest.class && !enumKeepRules) { + // EnumSet and EnumMap cannot be used without the enumKeepRules. + run.assertFailure(); + } else { + run.assertSuccess(); + assertLines2By2Correct(run.getStdOut()); + } } }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java index 8811ee5..daaf504 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
@@ -10,7 +10,7 @@ import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -19,17 +19,23 @@ @RunWith(Parameterized.class) public class FieldPutEnumUnboxingAnalysisTest extends EnumUnboxingTestBase { - private final TestParameters parameters; private static final Class<?>[] INPUTS = new Class<?>[] {InstanceFieldPut.class, StaticFieldPut.class}; - @Parameters(name = "{0}") - public static TestParametersCollection data() { + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final boolean enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { return enumUnboxingTestParameters(); } - public FieldPutEnumUnboxingAnalysisTest(TestParameters parameters) { + public FieldPutEnumUnboxingAnalysisTest( + TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) { this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; } @Test @@ -38,8 +44,8 @@ testForR8(parameters.getBackend()) .addInnerClasses(FieldPutEnumUnboxingAnalysisTest.class) .addKeepMainRules(INPUTS) - .addKeepRules(KEEP_ENUM) - .addOptionsModification(this::enableEnumOptions) + .addKeepRules(enumKeepRules ? KEEP_ENUM : "") + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) .allowDiagnosticInfoMessages() .enableInliningAnnotations() .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java index 2570a12..afd2e6a 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
@@ -6,7 +6,7 @@ import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -15,19 +15,24 @@ @RunWith(Parameterized.class) public class OrdinalEnumUnboxingAnalysisTest extends EnumUnboxingTestBase { - private final TestParameters parameters; + private static final Class<?> ENUM_CLASS = MyEnum.class; - @Parameters(name = "{0}") - public static TestParametersCollection data() { + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final boolean enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { return enumUnboxingTestParameters(); } - public OrdinalEnumUnboxingAnalysisTest(TestParameters parameters) { + public OrdinalEnumUnboxingAnalysisTest( + TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) { this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; } - private static final Class<?> ENUM_CLASS = MyEnum.class; - @Test public void testEnumUnboxing() throws Exception { Class<Ordinal> classToTest = Ordinal.class; @@ -35,8 +40,8 @@ testForR8(parameters.getBackend()) .addProgramClasses(classToTest, ENUM_CLASS) .addKeepMainRule(classToTest) - .addKeepRules(KEEP_ENUM) - .addOptionsModification(this::enableEnumOptions) + .addKeepRules(enumKeepRules ? KEEP_ENUM : "") + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) .allowDiagnosticInfoMessages() .setMinApi(parameters.getApiLevel()) .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java index 2abd358..b231d32 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
@@ -7,7 +7,7 @@ import com.android.tools.r8.NeverInline; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -16,19 +16,24 @@ @RunWith(Parameterized.class) public class PhiEnumUnboxingAnalysisTest extends EnumUnboxingTestBase { - private final TestParameters parameters; + private static final Class<?> ENUM_CLASS = MyEnum.class; - @Parameters(name = "{0}") - public static TestParametersCollection data() { + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final boolean enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { return enumUnboxingTestParameters(); } - public PhiEnumUnboxingAnalysisTest(TestParameters parameters) { + public PhiEnumUnboxingAnalysisTest( + TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) { this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; } - private static final Class<?> ENUM_CLASS = MyEnum.class; - @Test public void testEnumUnboxing() throws Exception { Class<?> classToTest = Phi.class; @@ -36,9 +41,9 @@ testForR8(parameters.getBackend()) .addProgramClasses(classToTest, ENUM_CLASS) .addKeepMainRule(classToTest) - .addKeepRules(KEEP_ENUM) + .addKeepRules(enumKeepRules ? KEEP_ENUM : "") .enableInliningAnnotations() - .addOptionsModification(this::enableEnumOptions) + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) .allowDiagnosticInfoMessages() .setMinApi(parameters.getApiLevel()) .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java index 53cddc6..a40dec0 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
@@ -7,7 +7,7 @@ import com.android.tools.r8.NeverInline; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -16,19 +16,24 @@ @RunWith(Parameterized.class) public class SwitchEnumUnboxingAnalysisTest extends EnumUnboxingTestBase { - private final TestParameters parameters; + private static final Class<?> ENUM_CLASS = MyEnum.class; - @Parameters(name = "{0}") - public static TestParametersCollection data() { + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final boolean enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { return enumUnboxingTestParameters(); } - public SwitchEnumUnboxingAnalysisTest(TestParameters parameters) { + public SwitchEnumUnboxingAnalysisTest( + TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) { this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; } - private static final Class<?> ENUM_CLASS = MyEnum.class; - @Test public void testEnumUnboxing() throws Exception { Class<Switch> classToTest = Switch.class; @@ -36,9 +41,9 @@ testForR8(parameters.getBackend()) .addInnerClasses(SwitchEnumUnboxingAnalysisTest.class) .addKeepMainRule(classToTest) - .addKeepRules(KEEP_ENUM) + .addKeepRules(enumKeepRules ? KEEP_ENUM : "") .enableInliningAnnotations() - .addOptionsModification(this::enableEnumOptions) + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) .allowDiagnosticInfoMessages() .setMinApi(parameters.getApiLevel()) .compile()
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java index fc6e7d3..fc8f3bf 100644 --- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java +++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.internal; import static com.android.tools.r8.ToolHelper.isLocalDevelopment; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; @@ -42,8 +43,13 @@ .addKeepRuleFiles( Paths.get(base).resolve(BASE_PG_CONF), Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS, PG_CONF)) + .allowDiagnosticInfoMessages() .allowUnusedProguardConfigurationRules() - .compile(); + .compile() + .assertAllInfoMessagesMatch( + anyOf( + equalTo("Ignoring option: -optimizations"), + containsString("Proguard configuration rule does not match anything"))); int appSize = compileResult.app.applicationSize(); assertTrue("Expected max size of " + MAX_SIZE+ ", got " + appSize, appSize < MAX_SIZE);
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java index 4484311..4c3085e 100644 --- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java +++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.dex.ApplicationReader; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppInfoWithSubtyping; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; @@ -36,7 +37,7 @@ private AndroidApp app; private DirectMappedDexApplication program; private AppInfoWithSubtyping appInfo; - private AppView<?> appView; + private AppView<? extends AppInfoWithClassHierarchy> appView; @Before public void readGMSCore() throws Exception { @@ -64,7 +65,8 @@ // Check lookup targets with include method. ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(clazz, method.method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView, appInfo); + LookupResult lookupResult = + resolutionResult.lookupVirtualDispatchTargets(clazz, appView, appInfo); assertTrue(lookupResult.isLookupResultSuccess()); Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets(); assertTrue(targets.contains(method)); @@ -74,7 +76,7 @@ LookupResult lookupResult = appInfo .resolveMethodOnInterface(clazz, method.method) - .lookupVirtualDispatchTargets(appView, appInfo); + .lookupVirtualDispatchTargets(clazz, appView, appInfo); assertTrue(lookupResult.isLookupResultSuccess()); Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets(); if (appInfo.subtypes(method.method.holder).stream() @@ -103,6 +105,6 @@ @Test public void testLookup() { - program.classes().forEach(this::testLookup); + program.classesWithDeterministicOrder().forEach(this::testLookup); } }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java index 5d48fa6..6104883 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -137,7 +137,7 @@ TypeLatticeElement.fromDexType( app.dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView), null); - instruction = new Argument(value, false); + instruction = new Argument(value, 0, false); instruction.setPosition(position); block0.add(instruction, metadata); instruction = new If(Type.EQ, value);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java index 9ccdb7f..9ebb411 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -70,12 +70,7 @@ // inlined into main(), which makes A.foo() eligible for inlining into main(). ClassSubject aClassSubject = inspector.clazz(A.class); assertThat(aClassSubject, isPresent()); - if (maxInliningDepth == 0) { - assertThat(aClassSubject.uniqueMethodWithName("foo"), isPresent()); - } else { - assert maxInliningDepth == 1; - assertThat(aClassSubject.uniqueMethodWithName("foo"), not(isPresent())); - } + assertThat(aClassSubject.uniqueMethodWithName("foo"), not(isPresent())); // A.bar() should always be inlined because it is marked as @AlwaysInline. assertThat(aClassSubject.uniqueMethodWithName("bar"), not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java index beecf65..ca34efd 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.ir.optimize.membervaluepropagation.fields; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverClassInline; @@ -50,8 +51,7 @@ ClassSubject testClassSubject = inspector.clazz(TestClass.class); assertThat(testClassSubject, isPresent()); assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent()); - // TODO(b/147652121): Should be absent. - assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent()); + assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent())); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java new file mode 100644 index 0000000..cf7650f --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
@@ -0,0 +1,99 @@ +// 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.membervaluepropagation.fields; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class FieldInitializedByDifferentConstantsInMultipleConstructorsTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public FieldInitializedByDifferentConstantsInMultipleConstructorsTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(FieldInitializedByDifferentConstantsInMultipleConstructorsTest.class) + .addKeepMainRule(TestClass.class) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Live!", "Live!"); + } + + private void inspect(CodeInspector inspector) { + ClassSubject testClassSubject = inspector.clazz(TestClass.class); + assertThat(testClassSubject, isPresent()); + assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent()); + // TODO(b/147652121): Should be absent. + assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent()); + } + + static class TestClass { + + public static void main(String[] args) { + A obj1 = new A(); + if (obj1.x == 42) { + live(); + } else { + dead(); + } + + A obj2 = new A(null); + if (obj2.x == 43) { + live(); + } else { + dead(); + } + } + + @NeverInline + static void live() { + System.out.println("Live!"); + } + + @NeverInline + static void dead() { + System.out.println("Dead!"); + } + } + + @NeverClassInline + static class A { + + int x; + + A() { + this.x = 42; + } + + A(Object unused) { + this.x = 43; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java new file mode 100644 index 0000000..2f0cf08 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
@@ -0,0 +1,99 @@ +// 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.membervaluepropagation.fields; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class FieldInitializedBySameConstantInMultipleConstructorsTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public FieldInitializedBySameConstantInMultipleConstructorsTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(FieldInitializedBySameConstantInMultipleConstructorsTest.class) + .addKeepMainRule(TestClass.class) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Live!", "Live!"); + } + + private void inspect(CodeInspector inspector) { + ClassSubject testClassSubject = inspector.clazz(TestClass.class); + assertThat(testClassSubject, isPresent()); + assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent()); + // TODO(b/147652121): Should be absent. + assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent()); + } + + static class TestClass { + + public static void main(String[] args) { + A obj1 = new A(); + if (obj1.x == 42) { + live(); + } else { + dead(); + } + + A obj2 = new A(null); + if (obj2.x == 42) { + live(); + } else { + dead(); + } + } + + @NeverInline + static void live() { + System.out.println("Live!"); + } + + @NeverInline + static void dead() { + System.out.println("Dead!"); + } + } + + @NeverClassInline + static class A { + + int x; + + A() { + this.x = 42; + } + + A(Object unused) { + this.x = 42; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java new file mode 100644 index 0000000..af6296b --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
@@ -0,0 +1,172 @@ +// 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.unusedarguments; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; +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 UnusedAndUninstantiatedTypesTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public UnusedAndUninstantiatedTypesTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testUnusedAndUninstantiatedTypes() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(UnusedAndUninstantiatedTypesTest.class) + .addKeepMainRule(Main.class) + .noMinification() + .enableInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::assertMethodsAreThere) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines( + "nothing", + "uninstantiatedThenUnused null", + "unusedThenUninstantiated null", + "doubleUninstantiatedThenUnused null and null", + "doubleUnusedThenUninstantiated null and null", + "tripleUninstantiatedThenUnused null and null and null", + "tripleUnusedThenUninstantiated null and null and null", + "withWideParameters null and null and null"); + } + + private void assertMethodsAreThere(CodeInspector i) { + List<FoundMethodSubject> methods = i.clazz(Main.class).allMethods(); + assertEquals(9, methods.size()); + for (FoundMethodSubject method : methods) { + if (!method.getMethod().method.name.toString().equals("main")) { + assertEquals(0, method.getMethod().method.getArity()); + } + } + } + + @SuppressWarnings("SameParameterValue") + static class Main { + + public static void main(String[] args) { + uninstantiatedAndUnused(null); + uninstantiatedThenUnused(null, new Unused()); + unusedThenUninstantiated(new Unused(), null); + doubleUninstantiatedThenUnused(null, new Unused(), null, new Unused()); + doubleUnusedThenUninstantiated(new Unused(), null, new Unused(), null); + tripleUninstantiatedThenUnused(null, new Unused(), new Unused(), null, null, new Unused()); + tripleUnusedThenUninstantiated(new Unused(), null, null, new Unused(), new Unused(), null); + withWideParameters(0L, 1L, null, null, 2L, 3L, null); + } + + @NeverInline + static void uninstantiatedAndUnused(UnInstantiated uninstantiated) { + System.out.println("nothing"); + } + + @NeverInline + static void uninstantiatedThenUnused(UnInstantiated uninstantiated, Unused unused) { + System.out.println("uninstantiatedThenUnused " + uninstantiated); + } + + @NeverInline + static void unusedThenUninstantiated(Unused unused, UnInstantiated uninstantiated) { + System.out.println("unusedThenUninstantiated " + uninstantiated); + } + + @NeverInline + static void doubleUninstantiatedThenUnused( + UnInstantiated uninstantiated1, + Unused unused1, + UnInstantiated uninstantiated2, + Unused unused2) { + System.out.println( + "doubleUninstantiatedThenUnused " + uninstantiated1 + " and " + uninstantiated2); + } + + @NeverInline + static void doubleUnusedThenUninstantiated( + Unused unused1, + UnInstantiated uninstantiated1, + Unused unused2, + UnInstantiated uninstantiated2) { + System.out.println( + "doubleUnusedThenUninstantiated " + uninstantiated1 + " and " + uninstantiated2); + } + + @NeverInline + static void tripleUninstantiatedThenUnused( + UnInstantiated uninstantiated1, + Unused unused0, + Unused unused1, + UnInstantiated uninstantiated0, + UnInstantiated uninstantiated2, + Unused unused2) { + System.out.println( + "tripleUninstantiatedThenUnused " + + uninstantiated1 + + " and " + + uninstantiated2 + + " and " + + uninstantiated0); + } + + @NeverInline + static void tripleUnusedThenUninstantiated( + Unused unused1, + UnInstantiated uninstantiated0, + UnInstantiated uninstantiated1, + Unused unused0, + Unused unused2, + UnInstantiated uninstantiated2) { + System.out.println( + "tripleUnusedThenUninstantiated " + + uninstantiated1 + + " and " + + uninstantiated2 + + " and " + + uninstantiated0); + } + + @NeverInline + static void withWideParameters( + long longUnused0, + long longUnused1, + UnInstantiated uninstantiated0, + UnInstantiated uninstantiated1, + long longUnused2, + long longUnused3, + UnInstantiated uninstantiated2) { + System.out.println( + "withWideParameters " + + uninstantiated1 + + " and " + + uninstantiated2 + + " and " + + uninstantiated0); + } + } + + private static class UnInstantiated {} + + private static class Unused {} +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java index f6649b7..eb98862 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -9,6 +9,7 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestParameters; @@ -20,6 +21,7 @@ import com.android.tools.r8.utils.codeinspector.KmClassSubject; import com.android.tools.r8.utils.codeinspector.KmFunctionSubject; import com.android.tools.r8.utils.codeinspector.KmPackageSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; import java.nio.file.Path; import java.util.Collection; import java.util.HashMap; @@ -100,6 +102,7 @@ ClassSubject impl = inspector.clazz(bClassName); assertThat(impl, isPresent()); assertThat(impl, not(isRenamed())); + // API entry is kept, hence the presence of Metadata. KmClassSubject kmClass = impl.getKmClass(); assertThat(kmClass, isPresent()); @@ -110,6 +113,7 @@ ClassSubject bKt = inspector.clazz(bKtClassName); assertThat(bKt, isPresent()); assertThat(bKt, not(isRenamed())); + // API entry is kept, hence the presence of Metadata. KmPackageSubject kmPackage = bKt.getKmPackage(); assertThat(kmPackage, isPresent()); @@ -128,7 +132,7 @@ .addKeepRules("-keep class **.B") .addKeepRules("-keep class **.I { <methods>; }") // Keep Super, but allow minification. - .addKeepRules("-keep,allowobfuscation class **.Super") + .addKeepRules("-keep,allowobfuscation class **.Super { <methods>; }") // Keep the BKt method, which will be called from other kotlin code. .addKeepRules("-keep class **.BKt { <methods>; }") .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS) @@ -158,11 +162,24 @@ ClassSubject sup = inspector.clazz(superClassName); assertThat(sup, isRenamed()); + MethodSubject foo = sup.uniqueMethodWithName("foo"); + assertThat(foo, isRenamed()); + + KmClassSubject kmClass = sup.getKmClass(); + assertThat(kmClass, isPresent()); + + // TODO(b/70169921): would be better to look up function with the original name, "foo". + KmFunctionSubject kmFunction = kmClass.kmFunctionWithUniqueName(foo.getFinalName()); + assertThat(kmFunction, isPresent()); + assertThat(kmFunction, not(isExtensionFunction())); + assertEquals(foo.getJvmMethodSignatureAsString(), kmFunction.signature().asString()); + ClassSubject impl = inspector.clazz(bClassName); assertThat(impl, isPresent()); assertThat(impl, not(isRenamed())); + // API entry is kept, hence the presence of Metadata. - KmClassSubject kmClass = impl.getKmClass(); + kmClass = impl.getKmClass(); assertThat(kmClass, isPresent()); List<ClassSubject> superTypes = kmClass.getSuperTypes(); assertTrue(superTypes.stream().noneMatch( @@ -177,7 +194,7 @@ KmPackageSubject kmPackage = bKt.getKmPackage(); assertThat(kmPackage, isPresent()); - KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("fun"); + kmFunction = kmPackage.kmFunctionWithUniqueName("fun"); assertThat(kmFunction, isPresent()); assertThat(kmFunction, not(isExtensionFunction())); }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java index 2dc57bf..5b9dfed 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -4,25 +4,34 @@ package com.android.tools.r8.kotlin.metadata; import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; +import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestParameters; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.shaking.ProguardKeepAttributes; +import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.codeinspector.AnnotationSubject; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.KmFunctionSubject; +import com.android.tools.r8.utils.codeinspector.KmPackageSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; import java.nio.file.Path; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; +import kotlinx.metadata.jvm.KotlinClassMetadata; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -88,7 +97,6 @@ private void inspectMerged(CodeInspector inspector) { String utilClassName = PKG + ".multifileclass_lib.UtilKt"; - String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt"; ClassSubject util = inspector.clazz(utilClassName); assertThat(util, isPresent()); @@ -98,22 +106,10 @@ assertThat(commaJoinOfInt, not(isRenamed())); MethodSubject joinOfInt = util.uniqueMethodWithName("joinOfInt"); assertThat(joinOfInt, not(isPresent())); - // API entry is kept, hence the presence of Metadata. - AnnotationSubject annotationSubject = util.annotation(METADATA_TYPE); - assertThat(annotationSubject, isPresent()); - // TODO(b/70169921): need further inspection. - ClassSubject signed = inspector.clazz(signedClassName); - assertThat(signed, isRenamed()); - commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt"); - assertThat(commaJoinOfInt, isPresent()); - assertThat(commaJoinOfInt, not(isRenamed())); - joinOfInt = signed.uniqueMethodWithName("joinOfInt"); - assertThat(joinOfInt, isRenamed()); - // API entry is kept, hence the presence of Metadata. - annotationSubject = util.annotation(METADATA_TYPE); - assertThat(annotationSubject, isPresent()); - // TODO(b/70169921): need further inspection. + inspectMetadataForFacade(inspector, util); + + inspectSignedKt(inspector); } @Test @@ -146,7 +142,6 @@ private void inspectRenamed(CodeInspector inspector) { String utilClassName = PKG + ".multifileclass_lib.UtilKt"; - String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt"; ClassSubject util = inspector.clazz(utilClassName); assertThat(util, isPresent()); @@ -157,21 +152,48 @@ MethodSubject joinOfInt = util.uniqueMethodWithName("joinOfInt"); assertThat(joinOfInt, isPresent()); assertThat(joinOfInt, isRenamed()); + + inspectMetadataForFacade(inspector, util); + + inspectSignedKt(inspector); + } + + private void inspectMetadataForFacade(CodeInspector inspector, ClassSubject util) { // API entry is kept, hence the presence of Metadata. AnnotationSubject annotationSubject = util.annotation(METADATA_TYPE); assertThat(annotationSubject, isPresent()); - // TODO(b/70169921): need further inspection. + KotlinClassMetadata metadata = util.getKotlinClassMetadata(); + assertNotNull(metadata); + assertTrue(metadata instanceof KotlinClassMetadata.MultiFileClassFacade); + KotlinClassMetadata.MultiFileClassFacade facade = + (KotlinClassMetadata.MultiFileClassFacade) metadata; + List<String> partClassNames = facade.getPartClassNames(); + assertEquals(2, partClassNames.size()); + for (String partClassName : partClassNames) { + ClassSubject partClass = + inspector.clazz(DescriptorUtils.getJavaTypeFromBinaryName(partClassName)); + assertThat(partClass, isRenamed()); + } + } + private void inspectSignedKt(CodeInspector inspector) { + String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt"; ClassSubject signed = inspector.clazz(signedClassName); assertThat(signed, isRenamed()); - commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt"); + MethodSubject commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt"); assertThat(commaJoinOfInt, isPresent()); assertThat(commaJoinOfInt, not(isRenamed())); - joinOfInt = signed.uniqueMethodWithName("joinOfInt"); + MethodSubject joinOfInt = signed.uniqueMethodWithName("joinOfInt"); assertThat(joinOfInt, isRenamed()); + // API entry is kept, hence the presence of Metadata. - annotationSubject = util.annotation(METADATA_TYPE); - assertThat(annotationSubject, isPresent()); - // TODO(b/70169921): need further inspection. + KmPackageSubject kmPackage = signed.getKmPackage(); + assertThat(kmPackage, isPresent()); + KmFunctionSubject kmFunction = + kmPackage.kmFunctionExtensionWithUniqueName("commaSeparatedJoinOfInt"); + assertThat(kmFunction, isPresent()); + assertThat(kmFunction, isExtensionFunction()); + // TODO(b/70169921): Inspect that parameter type has a correct type argument, Int. + // TODO(b/70169921): Inspect that the name in KmFunction is still 'join' so that apps can refer. } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt index d327fda..41d286c 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt
@@ -11,6 +11,10 @@ override fun doStuff() { println("do stuff") } + + fun foo() { + println("Super::foo") + } } class B : Super()
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java index ba27c33..72ede27 100644 --- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java +++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -223,7 +223,10 @@ appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); if (resolutionResult.isVirtualTarget()) { - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView, appInfo); + LookupResult lookupResult = + resolutionResult.lookupVirtualDispatchTargets( + appView.definitionForProgramType(buildType(Main.class, appView.dexItemFactory())), + appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets(); Set<DexType> targetHolders =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java index 3fcc33d..27da1c4 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -57,7 +58,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java index bdd5dd3..c8df559 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -55,7 +56,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java index 266cb48..9136188 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -19,6 +19,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -60,7 +61,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream() @@ -102,7 +105,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java new file mode 100644 index 0000000..da61d6a --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
@@ -0,0 +1,118 @@ +// 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.resolution.interfacetargets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NeverMerge; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.LookupResult; +import com.android.tools.r8.graph.ResolutionResult; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** This test is testing visiting direct subtypes and finding the right targets. */ +@RunWith(Parameterized.class) +public class DuplicateImportsTest extends TestBase { + + private static final String[] EXPECTED = new String[] {"J.foo"}; + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public DuplicateImportsTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testResolution() throws Exception { + assumeTrue(parameters.useRuntimeAsNoneRuntime()); + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(I.class, J.class, A.class, Main.class).build(), Main.class); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); + ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); + assertTrue(lookupResult.isLookupResultSuccess()); + Set<String> targets = + lookupResult.asLookupResultSuccess().getMethodTargets().stream() + .map(DexEncodedMethod::qualifiedName) + .collect(Collectors.toSet()); + ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo"); + assertEquals(expected, targets); + } + + @Test + public void testRuntime() throws IOException, CompilationFailedException, ExecutionException { + testForRuntime(parameters) + .addInnerClasses(DuplicateImportsTest.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testR8() throws IOException, CompilationFailedException, ExecutionException { + testForR8(parameters.getBackend()) + .addInnerClasses(DuplicateImportsTest.class) + .enableInliningAnnotations() + .enableMergeAnnotations() + .enableNeverClassInliningAnnotations() + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @NeverMerge + public interface I { + void foo(); + } + + @NeverMerge + public interface J extends I { + + @Override + @NeverInline + default void foo() { + System.out.println("J.foo"); + } + } + + @NeverClassInline + public static class A implements I, J {} + + public static class Main { + + public static void main(String[] args) { + ((I) new A()).foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java index 2614319..016347b 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.ToolHelper.DexVm; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.transformers.ClassTransformer; import com.android.tools.r8.utils.AndroidApiLevel; @@ -58,9 +59,14 @@ Main.class); AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "<clinit>", appInfo.dexItemFactory()); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); Assert.assertThrows( AssertionError.class, - () -> appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appView)); + () -> + appInfo + .resolveMethod(method.holder, method) + .lookupVirtualDispatchTargets(context, appView)); } private Matcher<String> getExpected() {
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java index 1ca966b..0ba0226 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.TestRuntime; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.DescriptorUtils; @@ -49,9 +50,14 @@ Main.class); AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory()); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); Assert.assertThrows( AssertionError.class, - () -> appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appView)); + () -> + appInfo + .resolveMethod(method.holder, method) + .lookupVirtualDispatchTargets(context, appView)); } @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java index 251fa37..eeca9fe 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -56,7 +57,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream() @@ -84,7 +87,6 @@ .enableNeverClassInliningAnnotations() .addKeepMainRule(Main.class) .setMinApi(parameters.getApiLevel()) - .compile() .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines(EXPECTED); }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java index eff0088..64c81fa 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -56,7 +57,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java index 9552212..a21d3e1 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -57,7 +58,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java index 41d4879..80caa62 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -17,6 +17,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -56,7 +57,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java index 84236e5..713d80e 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -17,6 +17,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -55,7 +56,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java index a952bb3..ba468e0 100644 --- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java +++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -17,6 +17,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -55,7 +56,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/NonAbstractWidening.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/NonAbstractWidening.java new file mode 100644 index 0000000..f9d75e4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/NonAbstractWidening.java
@@ -0,0 +1,16 @@ +// 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.resolution.packageprivate; + +import com.android.tools.r8.resolution.packageprivate.a.AbstractWidening; +import com.android.tools.r8.resolution.packageprivate.a.I; + +public class NonAbstractWidening extends AbstractWidening implements I { + + @Override + public void foo() { + System.out.println("Method declaration will be removed"); + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java new file mode 100644 index 0000000..a714a03 --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
@@ -0,0 +1,121 @@ +// 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.resolution.packageprivate; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.LookupResult; +import com.android.tools.r8.graph.ResolutionResult; +import com.android.tools.r8.resolution.packageprivate.a.Abstract; +import com.android.tools.r8.resolution.packageprivate.a.I; +import com.android.tools.r8.resolution.packageprivate.a.NonAbstract; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import org.junit.BeforeClass; +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 PackagePrivateClasspathWidenTest extends TestBase { + + private static final String[] EXPECTED = new String[] {"C.foo", "C.foo"}; + private static final Class[] CLASSPATH_CLASSES = + new Class[] {Abstract.class, NonAbstract.class, I.class}; + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public PackagePrivateClasspathWidenTest(TestParameters parameters) { + this.parameters = parameters; + } + + private static Path classPathJar = null; + + @BeforeClass + public static void createClassPathJar() throws IOException { + classPathJar = getStaticTemp().newFile("classpath.jar").toPath(); + writeClassesToJar(classPathJar, CLASSPATH_CLASSES); + } + + @Test + public void testResolution() throws Exception { + assumeTrue(parameters.useRuntimeAsNoneRuntime()); + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(C.class, Main.class).addClasspathFiles(classPathJar).build(), Main.class); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod method = buildNullaryVoidMethod(Abstract.class, "foo", appInfo.dexItemFactory()); + ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); + DexProgramClass context = + appView.definitionForProgramType(buildType(Abstract.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); + assertTrue(lookupResult.isLookupResultSuccess()); + Set<String> targets = + lookupResult.asLookupResultSuccess().getMethodTargets().stream() + .map(DexEncodedMethod::qualifiedName) + .collect(Collectors.toSet()); + ImmutableSet<String> expected = ImmutableSet.of(C.class.getTypeName() + ".foo"); + assertEquals(expected, targets); + } + + @Test + public void testRuntime() throws IOException, CompilationFailedException, ExecutionException { + testForRuntime(parameters) + .addProgramClasses(C.class, Main.class) + .addRunClasspathFiles(buildOnDexRuntime(parameters, classPathJar)) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testR8() throws IOException, CompilationFailedException, ExecutionException { + testForR8(parameters.getBackend()) + .addProgramClasses(C.class, Main.class) + .addClasspathClasses(CLASSPATH_CLASSES) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .addRunClasspathFiles(buildOnDexRuntime(parameters, classPathJar)) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + public static class C extends NonAbstract { + + @Override + public void foo() { + System.out.println("C.foo"); + } + } + + public static class Main { + + public static void main(String[] args) { + C c = new C(); + Abstract.run(c); + c.foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java index f465727..eb08197 100644 --- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.resolution.packageprivate.a.A; @@ -56,18 +57,18 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream() .map(DexEncodedMethod::qualifiedName) .collect(Collectors.toSet()); - // TODO(b/149363086): Fix expection, should not include C.bar(). ImmutableSet<String> expected = ImmutableSet.of( A.class.getTypeName() + ".bar", B.class.getTypeName() + ".bar", - C.class.getTypeName() + ".bar", D.class.getTypeName() + ".bar"); assertEquals(expected, targets); }
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java index 35791e7..619ee45 100644 --- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.resolution.packageprivate.PackagePrivateReentryTest.C; @@ -60,18 +61,18 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream() .map(DexEncodedMethod::qualifiedName) .collect(Collectors.toSet()); - // TODO(b/149363086): Fix expection, should not include C.bar(). ImmutableSet<String> expected = ImmutableSet.of( A.class.getTypeName() + ".bar", B.class.getTypeName() + ".bar", - C.class.getTypeName() + ".bar", D.class.getTypeName() + ".bar"); assertEquals(expected, targets); } @@ -107,7 +108,7 @@ private byte[] getDWithPackagePrivateFoo() throws NoSuchMethodException, IOException { return transformer(D.class) - .setAccessFlags(D.class.getDeclaredMethod("bar", null), m -> m.unsetPublic()) + .setAccessFlags(D.class.getDeclaredMethod("bar"), m -> m.unsetPublic()) .transform(); }
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java new file mode 100644 index 0000000..cc85b0d --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
@@ -0,0 +1,166 @@ +// 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.resolution.packageprivate; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.R8TestRunResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRunResult; +import com.android.tools.r8.ToolHelper.DexVm; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.LookupResult; +import com.android.tools.r8.graph.ResolutionResult; +import com.android.tools.r8.resolution.packageprivate.a.Abstract; +import com.android.tools.r8.resolution.packageprivate.a.AbstractWidening; +import com.android.tools.r8.resolution.packageprivate.a.I; +import com.android.tools.r8.resolution.packageprivate.a.J; +import com.android.tools.r8.resolution.packageprivate.a.NonAbstractWideningExtendingA; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.transformers.ClassTransformer; +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.MethodVisitor; + +@RunWith(Parameterized.class) +public class PackagePrivateWithDefaultMethod2Test extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public PackagePrivateWithDefaultMethod2Test(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testResolution() throws Exception { + assumeTrue(parameters.useRuntimeAsNoneRuntime()); + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses( + Abstract.class, + AbstractWidening.class, + I.class, + A.class, + NonAbstractWideningExtendingA.class, + J.class, + Main.class) + .addClassProgramData(getNonAbstractWithoutDeclaredMethods()) + .build(), + Main.class); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); + ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); + DexProgramClass context = + appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); + assertTrue(lookupResult.isLookupResultSuccess()); + Set<String> targets = + lookupResult.asLookupResultSuccess().getMethodTargets().stream() + .map(DexEncodedMethod::qualifiedName) + .collect(Collectors.toSet()); + // TODO(b/148591377): The set should be empty. + ImmutableSet<String> expected = ImmutableSet.of(AbstractWidening.class.getTypeName() + ".foo"); + assertEquals(expected, targets); + } + + @Test + public void testRuntime() throws ExecutionException, CompilationFailedException, IOException { + TestRunResult<?> runResult = + testForRuntime(parameters) + .addProgramClasses( + Abstract.class, + AbstractWidening.class, + I.class, + A.class, + NonAbstractWideningExtendingA.class, + J.class, + Main.class) + .addProgramClassFileData(getNonAbstractWithoutDeclaredMethods()) + .run(parameters.getRuntime(), Main.class); + if (parameters.isDexRuntime() + && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) { + runResult.assertFailure(); + } else { + runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError")); + } + } + + @Test + public void testR8() throws ExecutionException, CompilationFailedException, IOException { + R8TestRunResult runResult = + testForR8(parameters.getBackend()) + .addProgramClasses( + Abstract.class, + AbstractWidening.class, + I.class, + A.class, + NonAbstractWideningExtendingA.class, + J.class, + Main.class) + .addProgramClassFileData(getNonAbstractWithoutDeclaredMethods()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .run(parameters.getRuntime(), Main.class) + .disassemble(); + if (parameters.isDexRuntime() + && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) { + runResult.assertFailure(); + } else { + runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError")); + } + } + + private byte[] getNonAbstractWithoutDeclaredMethods() throws IOException { + return transformer(NonAbstractWidening.class) + .addClassTransformer( + new ClassTransformer() { + @Override + public MethodVisitor visitMethod( + int access, + String name, + String descriptor, + String signature, + String[] exceptions) { + if (!name.equals("foo")) { + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + return null; + } + }) + .transform(); + } + + public static class A extends NonAbstractWidening {} + + public static class Main { + + public static void main(String[] args) { + NonAbstractWideningExtendingA d = new NonAbstractWideningExtendingA(); + Abstract.run(d); + d.foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java index c2d041c..42efe5c 100644 --- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
@@ -19,6 +19,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.resolution.packageprivate.a.Abstract; @@ -71,7 +72,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java index 28c8c2f..b761626 100644 --- a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.resolution.packageprivate; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationFailedException; @@ -17,6 +16,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.resolution.packageprivate.a.A; @@ -57,18 +57,15 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); - assertTrue(lookupResult.isLookupResultSuccess()); + DexProgramClass context = + appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream() .map(DexEncodedMethod::qualifiedName) .collect(Collectors.toSet()); - // TODO(b/149363086): Fix expectation. ImmutableSet<String> expected = - ImmutableSet.of( - A.class.getTypeName() + ".bar", - B.class.getTypeName() + ".bar", - C.class.getTypeName() + ".bar"); + ImmutableSet.of(A.class.getTypeName() + ".bar", B.class.getTypeName() + ".bar"); assertEquals(expected, targets); }
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/a/AbstractWidening.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/AbstractWidening.java new file mode 100644 index 0000000..472a97b --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/AbstractWidening.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.resolution.packageprivate.a; + +public abstract class AbstractWidening extends Abstract { + + @Override + public abstract void foo(); +}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/a/NonAbstractWideningExtendingA.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/NonAbstractWideningExtendingA.java new file mode 100644 index 0000000..ec0bd6a --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/NonAbstractWideningExtendingA.java
@@ -0,0 +1,10 @@ +// 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.resolution.packageprivate.a; + +import com.android.tools.r8.resolution.packageprivate.PackagePrivateWithDefaultMethod2Test; + +public class NonAbstractWideningExtendingA extends PackagePrivateWithDefaultMethod2Test.A + implements J {}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java index 9538dec..88c7b9e 100644 --- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -56,7 +57,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java index 4133fbe..00ef1db 100644 --- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -56,15 +57,15 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream() .map(DexEncodedMethod::qualifiedName) .collect(Collectors.toSet()); - // TODO(b/148591377): I.foo() should ideally not be included in the set. - ImmutableSet<String> expected = - ImmutableSet.of(I.class.getTypeName() + ".foo", J.class.getTypeName() + ".foo"); + ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo"); assertEquals(expected, targets); }
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java index 264ddb0..3c30267 100644 --- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -56,7 +57,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java index ff58047..dccf199 100644 --- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -59,7 +60,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java new file mode 100644 index 0000000..da24bcb --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
@@ -0,0 +1,119 @@ +// 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.resolution.virtualtargets; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ResolutionResult; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.DescriptorUtils; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +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 InvalidResolutionToThisTarget extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public InvalidResolutionToThisTarget(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testResolution() throws Exception { + assumeTrue(parameters.useRuntimeAsNoneRuntime()); + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(A.class).addClassProgramData(getMainWithModifiedReceiverCall()).build(), + Main.class); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); + DexType mainType = buildType(Main.class, appInfo.dexItemFactory()); + ResolutionResult resolutionResult = appInfo.resolveMethod(mainType, method); + // TODO(b/149516194): This should be failed resolution. + assertFalse(resolutionResult.isFailedResolution()); + } + + @Test + public void testRuntime() throws IOException, CompilationFailedException, ExecutionException { + testForRuntime(parameters) + .addProgramClasses(A.class) + .addProgramClassFileData(getMainWithModifiedReceiverCall()) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatMatches(containsString("java.lang.VerifyError")); + } + + @Test + public void testR8() throws IOException, CompilationFailedException, ExecutionException { + CompilationFailedException compilationFailedException = + assertThrows( + CompilationFailedException.class, + () -> { + testForR8(parameters.getBackend()) + .addProgramClasses(A.class) + .addProgramClassFileData(getMainWithModifiedReceiverCall()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .compile(); + }); + } + + private byte[] getMainWithModifiedReceiverCall() throws IOException { + return transformer(Main.class) + .transformMethodInsnInMethod( + "main", + (opcode, owner, name, descriptor, isInterface, continuation) -> { + if (name.equals("foo")) { + continuation.apply( + opcode, + DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()), + name, + descriptor, + isInterface); + } else { + continuation.apply(opcode, owner, name, descriptor, isInterface); + } + }) + .transform(); + } + + public static class A { + + public void foo() { + System.out.println("A.foo"); + } + } + + public static class Main { + + public void foo() { + System.out.println("Main.foo"); + new A().foo(); + } + + public static void main(String[] args) { + new Main().foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java index 055c113..7d32527 100644 --- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -55,7 +56,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java index 45c208a..37c071a 100644 --- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
@@ -5,6 +5,9 @@ package com.android.tools.r8.resolution.virtualtargets; import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.TestBase; @@ -12,11 +15,21 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.TestRunResult; import com.android.tools.r8.ToolHelper.DexVm; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.LookupResult; +import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.resolution.virtualtargets.package_a.Middle; import com.android.tools.r8.resolution.virtualtargets.package_a.Top; import com.android.tools.r8.resolution.virtualtargets.package_a.TopRunner; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.ImmutableSet; import java.io.IOException; +import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -39,6 +52,30 @@ } @Test + public void testResolution() throws Exception { + assumeTrue(parameters.useRuntimeAsNoneRuntime()); + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(Top.class, Middle.class, Bottom.class, TopRunner.class, Main.class) + .build(), + Main.class); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod method = buildNullaryVoidMethod(Top.class, "clear", appInfo.dexItemFactory()); + ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); + DexProgramClass context = + appView.definitionForProgramType(buildType(TopRunner.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); + assertTrue(lookupResult.isLookupResultSuccess()); + Set<String> targets = + lookupResult.asLookupResultSuccess().getMethodTargets().stream() + .map(DexEncodedMethod::qualifiedName) + .collect(Collectors.toSet()); + ImmutableSet<String> expected = + ImmutableSet.of(Top.class.getTypeName() + ".clear", Middle.class.getTypeName() + ".clear"); + assertEquals(expected, targets); + } + + @Test public void testRuntime() throws ExecutionException, CompilationFailedException, IOException { TestRunResult<?> runResult = testForRuntime(parameters.getRuntime(), parameters.getApiLevel()) @@ -60,7 +97,7 @@ .addKeepMainRule(Main.class) .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines("Bottom.clear()", "Bottom.clear()"); + .assertFailureWithErrorThatMatches(containsString("AbstractMethodError")); } public static class Bottom extends Middle {
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java index e786da6..dfc9d43 100644 --- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
@@ -5,22 +5,34 @@ package com.android.tools.r8.resolution.virtualtargets; import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; -import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.TestRunResult; import com.android.tools.r8.ToolHelper.DexVm; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.LookupResult; +import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel; import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunner; import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunnerWithCast; +import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.DescriptorUtils; +import com.google.common.collect.ImmutableSet; import java.io.IOException; +import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -32,6 +44,9 @@ private static final String[] EXPECTED = new String[] {"ViewModel.clear()", "MyViewModel.clear()", "ViewModel.clear()"}; + private static final String[] R8_OUTPUT = + new String[] {"MyViewModel.clear()", "MyViewModel.clear()", "MyViewModel.clear()"}; + private final TestParameters parameters; @Parameters(name = "{0}") @@ -44,6 +59,30 @@ } @Test + public void testResolution() throws Exception { + assumeTrue(parameters.useRuntimeAsNoneRuntime()); + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(MyViewModel.class, ViewModel.class, Main.class, ViewModelRunner.class) + .build(), + Main.class); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory()); + ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); + DexProgramClass context = + appView.definitionForProgramType( + buildType(ViewModelRunner.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); + assertTrue(lookupResult.isLookupResultSuccess()); + Set<String> targets = + lookupResult.asLookupResultSuccess().getMethodTargets().stream() + .map(DexEncodedMethod::qualifiedName) + .collect(Collectors.toSet()); + ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear"); + assertEquals(expected, targets); + } + + @Test public void testRuntime() throws ExecutionException, CompilationFailedException, IOException { TestRunResult<?> runResult = testForRuntime(parameters.getRuntime(), parameters.getApiLevel()) @@ -60,19 +99,30 @@ @Test public void testR8() throws ExecutionException, CompilationFailedException, IOException { - R8TestRunResult runResult = - testForR8(parameters.getBackend()) - .addProgramClasses( - MyViewModel.class, Main.class, ViewModel.class, ViewModelRunner.class) - .addKeepMainRule(Main.class) - .setMinApi(parameters.getApiLevel()) - .run(parameters.getRuntime(), Main.class); - if (parameters.isDexRuntime() - && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) { - runResult.assertFailureWithErrorThatMatches(containsString("overrides final")); - } else { - runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException")); - } + // TODO(b/148429150): Fix R8 to output expected. + testForR8(parameters.getBackend()) + .addProgramClasses(MyViewModel.class, Main.class, ViewModel.class, ViewModelRunner.class) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(R8_OUTPUT); + } + + @Test + public void testResolutionWithInvalidInvoke() throws Exception { + assumeTrue(parameters.useRuntimeAsNoneRuntime()); + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(MyViewModel.class, ViewModel.class, Main.class, ViewModelRunner.class) + .build(), + Main.class); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory()); + ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); + assertTrue(lookupResult.isLookupResultFailure()); } @Test @@ -94,19 +144,39 @@ @Test public void testR8WithInvalidInvoke() throws ExecutionException, CompilationFailedException, IOException { - R8TestRunResult runResult = - testForR8(parameters.getBackend()) - .addProgramClasses(MyViewModel.class, ViewModel.class, ViewModelRunner.class) - .addProgramClassFileData(getModifiedMainWithIllegalInvokeToViewModelClear()) - .addKeepMainRule(Main.class) - .setMinApi(parameters.getApiLevel()) - .run(parameters.getRuntime(), Main.class); - if (parameters.isDexRuntime() - && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) { - runResult.assertFailureWithErrorThatMatches(containsString("overrides final")); - } else { - runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException")); - } + // TODO(b/148429150): Fix R8 to output expected. + testForR8(parameters.getBackend()) + .addProgramClasses(MyViewModel.class, ViewModel.class, ViewModelRunner.class) + .addProgramClassFileData(getModifiedMainWithIllegalInvokeToViewModelClear()) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException")); + } + + @Test + public void testResolutionWithAmbiguousInvoke() throws Exception { + assumeTrue(parameters.useRuntimeAsNoneRuntime()); + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses( + MyViewModel.class, ViewModel.class, Main.class, ViewModelRunnerWithCast.class) + .build(), + Main.class); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory()); + ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); + DexProgramClass context = + appView.definitionForProgramType( + buildType(ViewModelRunnerWithCast.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); + assertTrue(lookupResult.isLookupResultSuccess()); + Set<String> targets = + lookupResult.asLookupResultSuccess().getMethodTargets().stream() + .map(DexEncodedMethod::qualifiedName) + .collect(Collectors.toSet()); + ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear"); + assertEquals(expected, targets); } @Test @@ -129,19 +199,14 @@ @Test public void testR8WithAmbiguousInvoke() throws ExecutionException, CompilationFailedException, IOException { - R8TestRunResult runResult = - testForR8(parameters.getBackend()) - .addProgramClasses(MyViewModel.class, ViewModel.class, Main.class) - .addProgramClassFileData(getModifiedViewModelRunnerWithDirectMyViewModelTarget()) - .addKeepMainRule(Main.class) - .setMinApi(parameters.getApiLevel()) - .run(parameters.getRuntime(), Main.class); - if (parameters.isDexRuntime() - && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) { - runResult.assertFailureWithErrorThatMatches(containsString("overrides final")); - } else { - runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException")); - } + // TODO(b/148429150): Fix R8 to output expected. + testForR8(parameters.getBackend()) + .addProgramClasses(MyViewModel.class, ViewModel.class, Main.class) + .addProgramClassFileData(getModifiedViewModelRunnerWithDirectMyViewModelTarget()) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(R8_OUTPUT); } private byte[] getModifiedMainWithIllegalInvokeToViewModelClear() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java new file mode 100644 index 0000000..915cd21 --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
@@ -0,0 +1,149 @@ +// 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.resolution.virtualtargets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.LookupResult; +import com.android.tools.r8.graph.ResolutionResult; +import com.android.tools.r8.resolution.virtualtargets.package_a.A; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Box; +import com.android.tools.r8.utils.DescriptorUtils; +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class PrivateOverrideOfVirtualTargetTest extends TestBase { + + private final TestParameters parameters; + private static final String[] EXPECTED = + new String[] {"A.foo", "A.bar", "B.foo", "A.bar", "B.bar"}; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public PrivateOverrideOfVirtualTargetTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testResolution() throws Exception { + assumeTrue(parameters.useRuntimeAsNoneRuntime()); + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(A.class, Main.class) + .addClassProgramData(getBWithModifiedInvokes()) + .build(), + DefaultWithoutTopTest.Main.class); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory()); + ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); + DexProgramClass context = + appView.definitionForProgramType(buildType(B.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); + assertTrue(lookupResult.isLookupResultSuccess()); + Set<String> targets = + lookupResult.asLookupResultSuccess().getMethodTargets().stream() + .map(DexEncodedMethod::qualifiedName) + .collect(Collectors.toSet()); + ImmutableSet<String> expected = ImmutableSet.of(A.class.getTypeName() + ".bar"); + assertEquals(expected, targets); + } + + @Test + public void testRuntime() + throws NoSuchMethodException, IOException, CompilationFailedException, ExecutionException { + testForRuntime(parameters) + .addProgramClasses(A.class, Main.class) + .addProgramClassFileData(getBWithModifiedInvokes()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testR8() + throws NoSuchMethodException, IOException, CompilationFailedException, ExecutionException { + testForR8(parameters.getBackend()) + .addProgramClasses(A.class, Main.class) + .addProgramClassFileData(getBWithModifiedInvokes()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + private byte[] getBWithModifiedInvokes() throws NoSuchMethodException, IOException { + Box<Boolean> modifyOwner = new Box<>(true); + return transformer(B.class) + .setPrivate(B.class.getDeclaredMethod("bar")) + .transformMethodInsnInMethod( + "callOnB", + (opcode, owner, name, descriptor, isInterface, continuation) -> { + if (name.equals("foo")) { + continuation.apply(opcode, owner, name, descriptor, isInterface); + return; + } + if (modifyOwner.get()) { + continuation.apply( + Opcodes.INVOKEVIRTUAL, + DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()), + name, + descriptor, + isInterface); + modifyOwner.set(false); + } else { + continuation.apply(Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface); + } + }) + .transform(); + } + + public static class B extends A { + private void foo() { + System.out.println("B.foo"); + } + + @Override + protected /* private */ void bar() { + System.out.println("B.bar"); + } + + void callOnB() { + foo(); + bar(); /* will become A.bar() */ + bar(); /* will become B.bar() */ + } + } + + public static class Main { + + public static void main(String[] args) { + B b = new B(); + A.run(b); + b.callOnB(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java index 570527c..3afeb68 100644 --- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.LookupResult; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -56,7 +57,9 @@ AppInfoWithLiveness appInfo = appView.appInfo(); DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); - LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView); + DexProgramClass context = + appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory())); + LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView); assertTrue(lookupResult.isLookupResultSuccess()); Set<String> targets = lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/A.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/A.java new file mode 100644 index 0000000..1b311d5 --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/A.java
@@ -0,0 +1,21 @@ +// 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.resolution.virtualtargets.package_a; + +public class A { + + void foo() { + System.out.println("A.foo"); + } + + protected void bar() { + System.out.println("A.bar"); + } + + public static void run(A a) { + a.foo(); + a.bar(); + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java index af204ec..5f6a618 100644 --- a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java +++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -31,7 +31,7 @@ public static void main(String[] args) throws Exception { C obj = new C(); for (Method m : obj.getClass().getDeclaredMethods()) { - m.invoke(obj, null); + m.invoke(obj); } } }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java index a471b18..7331a22 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.graph.DexClass; import java.util.List; import java.util.function.Consumer; +import kotlinx.metadata.jvm.KotlinClassMetadata; public class AbsentClassSubject extends ClassSubject { @@ -147,4 +148,9 @@ public KmPackageSubject getKmPackage() { return null; } + + @Override + public KotlinClassMetadata getKotlinClassMetadata() { + return null; + } }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java index d72d1a8..b693ad9 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -18,6 +18,7 @@ import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; +import kotlinx.metadata.jvm.KotlinClassMetadata; public abstract class ClassSubject extends Subject { @@ -174,4 +175,6 @@ public abstract KmClassSubject getKmClass(); public abstract KmPackageSubject getKmPackage(); + + public abstract KotlinClassMetadata getKotlinClassMetadata(); }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java index c0dba89..dc65b71 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -355,8 +355,25 @@ KotlinClassMetadata metadata = KotlinClassMetadataReader.toKotlinClassMetadata( codeInspector.getFactory().kotlin, annotationSubject.getAnnotation()); - assertTrue(metadata instanceof KotlinClassMetadata.FileFacade); - KotlinClassMetadata.FileFacade kFile = (KotlinClassMetadata.FileFacade) metadata; - return new FoundKmPackageSubject(codeInspector, getDexClass(), kFile.toKmPackage()); + assertTrue(metadata instanceof KotlinClassMetadata.FileFacade + || metadata instanceof KotlinClassMetadata.MultiFileClassPart); + if (metadata instanceof KotlinClassMetadata.FileFacade) { + KotlinClassMetadata.FileFacade kFile = (KotlinClassMetadata.FileFacade) metadata; + return new FoundKmPackageSubject(codeInspector, getDexClass(), kFile.toKmPackage()); + } else { + KotlinClassMetadata.MultiFileClassPart kPart = + (KotlinClassMetadata.MultiFileClassPart) metadata; + return new FoundKmPackageSubject(codeInspector, getDexClass(), kPart.toKmPackage()); + } + } + + @Override + public KotlinClassMetadata getKotlinClassMetadata() { + AnnotationSubject annotationSubject = annotation(METADATA_TYPE); + if (!annotationSubject.isPresent()) { + return null; + } + return KotlinClassMetadataReader.toKotlinClassMetadata( + codeInspector.getFactory().kotlin, annotationSubject.getAnnotation()); } }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java index b9fa99a..5b3338b 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -80,6 +80,7 @@ } } + // TODO(b/70169921): Search both original and renamed names. default KmFunctionSubject kmFunctionOrExtensionWithUniqueName(String name, boolean isExtension) { KmFunction foundFunction = null; for (KmFunction kmFunction : getKmDeclarationContainer().getFunctions()) {
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py index cf26883..abd0f71 100755 --- a/tools/run_on_as_app.py +++ b/tools/run_on_as_app.py
@@ -1224,6 +1224,7 @@ assert shrinker in SHRINKERS else: options.shrinker = [shrinker for shrinker in SHRINKERS] + if options.hash or options.version: # No need to build R8 if a specific version should be used. options.no_build = True @@ -1266,7 +1267,7 @@ options.no_logging = False # TODO(b/141081520): Remove logging filter once fixed. options.app_logging_filter = ['sqldelight'] - options.shrinker = [shrinker for shrinker in SHRINKERS if shrinker != 'pg'] + options.shrinker = ['r8', 'r8-full'] print(options.shrinker) if options.golem: