Reland "Postpone InterfaceProcessor interface modifications"
This reverts commit f7024558c818d88df3facc8528f973c298b42728.
Bug: 183998768
Change-Id: I3a0f126f2220ec8f65559db5adcbfd3917d66b81
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 9d9a502..956cd2c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -43,7 +43,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
+import java.util.function.Function;
import org.objectweb.asm.Opcodes;
/**
@@ -340,17 +342,17 @@
private final boolean needsLibraryInfo;
// Mapping from program and classpath classes to their information summary.
- private final Map<DexClass, ClassInfo> classInfo = new IdentityHashMap<>();
+ private final Map<DexClass, ClassInfo> classInfo = new ConcurrentHashMap<>();
// Mapping from library classes to their information summary.
- private final Map<DexLibraryClass, SignaturesInfo> libraryClassInfo = new IdentityHashMap<>();
+ private final Map<DexLibraryClass, SignaturesInfo> libraryClassInfo = new ConcurrentHashMap<>();
// Mapping from arbitrary interfaces to an information summary.
- private final Map<DexClass, SignaturesInfo> interfaceInfo = new IdentityHashMap<>();
+ private final Map<DexClass, SignaturesInfo> interfaceInfo = new ConcurrentHashMap<>();
// Mapping from actual program classes to the synthesized forwarding methods to be created.
private final Map<DexProgramClass, ProgramMethodSet> newSyntheticMethods =
- new IdentityHashMap<>();
+ new ConcurrentHashMap<>();
ClassProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
this.appView = appView;
@@ -375,12 +377,9 @@
@Override
public void process(DexProgramClass clazz, ProgramMethodSet synthesizedMethods) {
- visitClassInfo(clazz, new ReportingContext(clazz, clazz));
- }
-
- @Override
- public boolean shouldProcess(DexProgramClass clazz) {
- return !clazz.isInterface();
+ if (!clazz.isInterface()) {
+ visitClassInfo(clazz, new ReportingContext(clazz, clazz));
+ }
}
// We introduce forwarding methods only once all desugaring has been performed to avoid
@@ -816,6 +815,26 @@
return clazz;
}
+ // We cannot use ConcurrentHashMap computeIfAbsent because the calls are recursive ending in
+ // concurrent modification of the ConcurrentHashMap while performing computeIfAbsent.
+ private <C extends DexClass, I> I reentrantComputeIfAbsent(
+ Map<C, I> infoMap, C clazz, Function<C, I> supplier) {
+ // Try to avoid the synchronization when possible.
+ I computedInfo = infoMap.get(clazz);
+ if (computedInfo != null) {
+ return computedInfo;
+ }
+ synchronized (clazz) {
+ computedInfo = infoMap.get(clazz);
+ if (computedInfo != null) {
+ return computedInfo;
+ }
+ computedInfo = supplier.apply(clazz);
+ infoMap.put(clazz, computedInfo);
+ return computedInfo;
+ }
+ }
+
private ClassInfo visitClassInfo(DexType type, ReportingContext context) {
DexClass clazz = definitionOrNull(type, context);
return clazz == null ? ClassInfo.EMPTY : visitClassInfo(clazz, context);
@@ -826,7 +845,7 @@
if (clazz.isLibraryClass()) {
return ClassInfo.EMPTY;
}
- return classInfo.computeIfAbsent(clazz, key -> visitClassInfoRaw(key, context));
+ return reentrantComputeIfAbsent(classInfo, clazz, key -> visitClassInfoRaw(key, context));
}
private ClassInfo visitClassInfoRaw(DexClass clazz, ReportingContext context) {
@@ -857,7 +876,8 @@
private SignaturesInfo visitLibraryClassInfo(DexClass clazz) {
assert !clazz.isInterface();
return clazz.isLibraryClass()
- ? libraryClassInfo.computeIfAbsent(clazz.asLibraryClass(), this::visitLibraryClassInfoRaw)
+ ? reentrantComputeIfAbsent(
+ libraryClassInfo, clazz.asLibraryClass(), this::visitLibraryClassInfoRaw)
: SignaturesInfo.EMPTY;
}
@@ -879,7 +899,8 @@
if (iface.isLibraryClass() && ignoreLibraryInfo()) {
return SignaturesInfo.EMPTY;
}
- return interfaceInfo.computeIfAbsent(iface, key -> visitInterfaceInfoRaw(key, context));
+ return reentrantComputeIfAbsent(
+ interfaceInfo, iface, key -> visitInterfaceInfoRaw(key, context));
}
private SignaturesInfo visitInterfaceInfoRaw(DexClass iface, ReportingContext context) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
index 0a56ddb..e8f4756 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
@@ -274,15 +274,10 @@
}
@Override
- public boolean shouldProcess(DexProgramClass clazz) {
- return appView.options().isDesugaredLibraryCompilation()
- && rewriter.isEmulatedInterface(clazz.type);
- }
-
- @Override
public void process(DexProgramClass emulatedInterface, ProgramMethodSet synthesizedMethods) {
- assert rewriter.isEmulatedInterface(emulatedInterface.type);
- if (appView.isAlreadyLibraryDesugared(emulatedInterface)) {
+ if (!appView.options().isDesugaredLibraryCompilation()
+ || !rewriter.isEmulatedInterface(emulatedInterface.type)
+ || appView.isAlreadyLibraryDesugared(emulatedInterface)) {
return;
}
generateEmulateInterfaceLibrary(emulatedInterface);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
index 821ff44..c429fc7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
@@ -10,8 +10,6 @@
public interface InterfaceDesugaringProcessor {
- boolean shouldProcess(DexProgramClass clazz);
-
// The process phase can be performed before, concurrently or after code desugaring. It can be
// executed concurrently on all classes. Each processing may require to read other classes,
// so this phase cannot modify the classes themselves (for example insertion/removal of methods).
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index c45a1e0..0661c1d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -74,9 +74,14 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
@@ -128,9 +133,8 @@
// The emulatedMethod set is there to avoid doing the emulated look-up too often.
private final Set<DexString> emulatedMethods = Sets.newIdentityHashSet();
- // All forwarding methods generated during desugaring. We don't synchronize access
- // to this collection since it is only filled in ClassProcessor running synchronously.
- private final SortedProgramMethodSet synthesizedMethods = SortedProgramMethodSet.create();
+ // All forwarding methods and all throwing methods generated during desugaring.
+ private final ProgramMethodSet synthesizedMethods = ProgramMethodSet.createConcurrent();
// Caches default interface method info for already processed interfaces.
private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
@@ -977,16 +981,17 @@
// classes if needed.
InterfaceProcessor interfaceProcessor = new InterfaceProcessor(appView, this);
- // TODO(b/183998768): Merge the following loops into a single one.
- process(emulatedInterfaceProcessor, builder, flavour);
- process(classProcessor, builder, flavour);
- process(interfaceProcessor, builder, flavour);
+ // The interface processors must be ordered so that finalization of the processing is performed
+ // in that order. The emulatedInterfaceProcessor has to be last at this point to avoid renaming
+ // emulated interfaces before the other processing.
+ ImmutableList<InterfaceDesugaringProcessor> orderedInterfaceDesugaringProcessors =
+ ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
+ processClassesConcurrently(
+ orderedInterfaceDesugaringProcessors, builder, flavour, executorService);
- classProcessor.finalizeProcessing(builder, synthesizedMethods);
- interfaceProcessor.finalizeProcessing(builder, synthesizedMethods);
- emulatedInterfaceProcessor.finalizeProcessing(builder, synthesizedMethods);
-
- converter.processMethodsConcurrently(synthesizedMethods, executorService);
+ SortedProgramMethodSet sortedSynthesizedMethods = SortedProgramMethodSet.create();
+ sortedSynthesizedMethods.addAll(synthesizedMethods);
+ converter.processMethodsConcurrently(sortedSynthesizedMethods, executorService);
// Cached data is not needed any more.
clear();
@@ -1004,9 +1009,29 @@
return (!clazz.originatesFromDexResource() || flavour == Flavor.IncludeAllResources);
}
+ private void processClassesConcurrently(
+ List<InterfaceDesugaringProcessor> processors,
+ Builder<?> builder,
+ Flavor flavour,
+ ExecutorService executorService)
+ throws ExecutionException {
+ ThreadUtils.processItems(
+ Iterables.filter(
+ builder.getProgramClasses(), (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
+ clazz -> {
+ for (InterfaceDesugaringProcessor processor : processors) {
+ processor.process(clazz, synthesizedMethods);
+ }
+ },
+ executorService);
+ for (InterfaceDesugaringProcessor processor : processors) {
+ processor.finalizeProcessing(builder, synthesizedMethods);
+ }
+ }
+
private void process(InterfaceDesugaringProcessor processor, Builder<?> builder, Flavor flavour) {
for (DexProgramClass clazz : builder.getProgramClasses()) {
- if (shouldProcess(clazz, flavour) && processor.shouldProcess(clazz)) {
+ if (shouldProcess(clazz, flavour)) {
processor.process(clazz, synthesizedMethods);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 0bc86a3..83f051d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -60,6 +60,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import org.objectweb.asm.Opcodes;
// Default and static method interface desugaring processor for interfaces.
@@ -72,36 +73,34 @@
private final AppView<?> appView;
private final InterfaceMethodRewriter rewriter;
- private final InterfaceProcessorNestedGraphLens.Builder graphLensBuilder;
+ private final Map<DexProgramClass, PostProcessingInterfaceInfo> postProcessingInterfaceInfos =
+ new ConcurrentHashMap<>();
// All created companion classes indexed by interface type.
- final Map<DexClass, DexProgramClass> syntheticClasses = new IdentityHashMap<>();
+ final Map<DexClass, DexProgramClass> syntheticClasses = new ConcurrentHashMap<>();
InterfaceProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
this.appView = appView;
this.rewriter = rewriter;
- graphLensBuilder = InterfaceProcessorNestedGraphLens.builder();
- }
-
- @Override
- public boolean shouldProcess(DexProgramClass clazz) {
- return clazz.isInterface();
}
@Override
public void process(DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
- assert iface.isInterface();
+ if (!iface.isInterface()) {
+ return;
+ }
+
// The list of methods to be created in companion class.
List<DexEncodedMethod> companionMethods = new ArrayList<>();
ensureCompanionClassInitializesInterface(iface, companionMethods);
// Process virtual interface methods first.
- processVirtualInterfaceMethods(iface, companionMethods, graphLensBuilder);
+ processVirtualInterfaceMethods(iface, companionMethods);
// Process static and private methods, move them into companion class as well,
// make private instance methods public static.
- processDirectInterfaceMethods(iface, companionMethods, graphLensBuilder);
+ processDirectInterfaceMethods(iface, companionMethods);
if (companionMethods.isEmpty()) {
return; // No methods to create, companion class not needed.
@@ -221,10 +220,7 @@
}
private void processVirtualInterfaceMethods(
- DexProgramClass iface,
- List<DexEncodedMethod> companionMethods,
- InterfaceProcessorNestedGraphLens.Builder graphLensBuilder) {
- List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
+ DexProgramClass iface, List<DexEncodedMethod> companionMethods) {
for (ProgramMethod method : iface.virtualProgramMethods()) {
DexEncodedMethod virtual = method.getDefinition();
if (rewriter.isDefaultMethod(virtual)) {
@@ -260,28 +256,19 @@
code,
true);
implMethod.copyMetadata(virtual);
- virtual.setDefaultInterfaceMethodImplementation(implMethod);
companionMethods.add(implMethod);
- graphLensBuilder.recordCodeMovedToCompanionClass(
- method.getReference(), implMethod.getReference());
+ getPostProcessingInterfaceInfo(iface)
+ .mapDefaultMethodToCompanionMethod(virtual, implMethod);
}
- // Remove bridge methods.
- if (interfaceMethodRemovalChangesApi(virtual, iface)) {
- newVirtualMethods.add(virtual);
+ if (!interfaceMethodRemovalChangesApi(virtual, iface)) {
+ getPostProcessingInterfaceInfo(iface).setHasBridgesToRemove();
}
}
-
- // If at least one bridge method was removed then update the table.
- if (newVirtualMethods.size() < iface.getMethodCollection().numberOfVirtualMethods()) {
- iface.setVirtualMethods(newVirtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
- }
}
private void processDirectInterfaceMethods(
- DexProgramClass iface,
- List<DexEncodedMethod> companionMethods,
- InterfaceProcessorNestedGraphLens.Builder graphLensBuilder) {
+ DexProgramClass iface, List<DexEncodedMethod> companionMethods) {
DexEncodedMethod clinit = null;
for (ProgramMethod method : iface.directProgramMethods()) {
DexEncodedMethod definition = method.getDefinition();
@@ -322,7 +309,7 @@
true);
implMethod.copyMetadata(definition);
companionMethods.add(implMethod);
- graphLensBuilder.move(oldMethod, companionMethod);
+ getPostProcessingInterfaceInfo(iface).moveMethod(oldMethod, companionMethod);
continue;
}
@@ -354,9 +341,18 @@
true);
implMethod.copyMetadata(definition);
companionMethods.add(implMethod);
- graphLensBuilder.move(oldMethod, companionMethod);
+ getPostProcessingInterfaceInfo(iface).moveMethod(oldMethod, companionMethod);
}
+ boolean hasNonClinitDirectMethods =
+ iface.getMethodCollection().size() != (clinit == null ? 0 : 1);
+ if (hasNonClinitDirectMethods) {
+ getPostProcessingInterfaceInfo(iface).setHasNonClinitDirectMethods();
+ }
+ }
+
+ private void clearDirectMethods(DexProgramClass iface) {
+ DexEncodedMethod clinit = iface.getClassInitializer();
MethodCollection methodCollection = iface.getMethodCollection();
if (clinit != null) {
methodCollection.setSingleDirectMethod(clinit);
@@ -445,9 +441,57 @@
&& !rewriter.factory.isClassConstructor(method.getReference());
}
+ private InterfaceProcessorNestedGraphLens postProcessInterfaces() {
+ InterfaceProcessorNestedGraphLens.Builder graphLensBuilder =
+ InterfaceProcessorNestedGraphLens.builder();
+ postProcessingInterfaceInfos.forEach(
+ (iface, info) -> {
+ if (info.hasNonClinitDirectMethods()) {
+ clearDirectMethods(iface);
+ }
+ if (info.hasDefaultMethodsToImplementationMap()) {
+ info.getDefaultMethodsToImplementation()
+ .forEach(
+ (defaultMethod, companionMethod) -> {
+ defaultMethod.setDefaultInterfaceMethodImplementation(companionMethod);
+ graphLensBuilder.recordCodeMovedToCompanionClass(
+ defaultMethod.getReference(), companionMethod.getReference());
+ });
+ }
+ if (info.hasMethodsToMove()) {
+ info.getMethodsToMove().forEach(graphLensBuilder::move);
+ }
+ if (info.hasBridgesToRemove()) {
+ removeBridges(iface);
+ }
+ });
+ return graphLensBuilder.build(appView);
+ }
+
+ private void removeBridges(DexProgramClass iface) {
+ List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
+ for (ProgramMethod method : iface.virtualProgramMethods()) {
+ DexEncodedMethod virtual = method.getDefinition();
+ // Remove bridge methods.
+ if (interfaceMethodRemovalChangesApi(virtual, iface)) {
+ newVirtualMethods.add(virtual);
+ }
+ }
+
+ // If at least one bridge method was removed then update the table.
+ if (newVirtualMethods.size() < iface.getMethodCollection().numberOfVirtualMethods()) {
+ iface.setVirtualMethods(newVirtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
+ } else {
+ assert false
+ : "Interface "
+ + iface
+ + " was analysed as having bridges to remove, but no bridges were found.";
+ }
+ }
+
@Override
public void finalizeProcessing(Builder<?> builder, ProgramMethodSet synthesizedMethods) {
- InterfaceProcessorNestedGraphLens graphLens = graphLensBuilder.build(appView);
+ InterfaceProcessorNestedGraphLens graphLens = postProcessInterfaces();
if (appView.enableWholeProgramOptimizations() && graphLens != null) {
appView.setGraphLens(graphLens);
}
@@ -461,6 +505,65 @@
new InterfaceMethodRewriterFixup(appView, graphLens).run();
}
+ private PostProcessingInterfaceInfo getPostProcessingInterfaceInfo(DexProgramClass iface) {
+ return postProcessingInterfaceInfos.computeIfAbsent(
+ iface, ignored -> new PostProcessingInterfaceInfo());
+ }
+
+ static class PostProcessingInterfaceInfo {
+ private Map<DexEncodedMethod, DexEncodedMethod> defaultMethodsToImplementation;
+ private Map<DexMethod, DexMethod> methodsToMove;
+ private boolean hasNonClinitDirectMethods;
+ private boolean hasBridgesToRemove;
+
+ public void mapDefaultMethodToCompanionMethod(
+ DexEncodedMethod defaultMethod, DexEncodedMethod companionMethod) {
+ if (defaultMethodsToImplementation == null) {
+ defaultMethodsToImplementation = new IdentityHashMap<>();
+ }
+ defaultMethodsToImplementation.put(defaultMethod, companionMethod);
+ }
+
+ public Map<DexEncodedMethod, DexEncodedMethod> getDefaultMethodsToImplementation() {
+ return defaultMethodsToImplementation;
+ }
+
+ boolean hasDefaultMethodsToImplementationMap() {
+ return defaultMethodsToImplementation != null;
+ }
+
+ public void moveMethod(DexMethod ifaceMethod, DexMethod companionMethod) {
+ if (methodsToMove == null) {
+ methodsToMove = new IdentityHashMap<>();
+ }
+ methodsToMove.put(ifaceMethod, companionMethod);
+ }
+
+ public Map<DexMethod, DexMethod> getMethodsToMove() {
+ return methodsToMove;
+ }
+
+ public boolean hasMethodsToMove() {
+ return methodsToMove != null;
+ }
+
+ boolean hasNonClinitDirectMethods() {
+ return hasNonClinitDirectMethods;
+ }
+
+ void setHasNonClinitDirectMethods() {
+ hasNonClinitDirectMethods = true;
+ }
+
+ boolean hasBridgesToRemove() {
+ return hasBridgesToRemove;
+ }
+
+ void setHasBridgesToRemove() {
+ hasBridgesToRemove = true;
+ }
+ }
+
// Specific lens which remaps invocation types to static since all rewrites performed here
// are to static companion methods.
public static class InterfaceProcessorNestedGraphLens extends NestedGraphLens {