Logging to determine limitations of vertical class merger

Change-Id: If764b73f4f4b58c3bacb916b1340feb57fbb6bba
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 ed36a70..24433cc 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterators;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -68,6 +69,70 @@
  */
 public class VerticalClassMerger {
 
+  private enum AbortReason {
+    ALREADY_MERGED,
+    ALWAYS_INLINE,
+    CONFLICT,
+    ILLEGAL_ACCESS,
+    NO_SIDE_EFFECTS,
+    PINNED_SOURCE,
+    PINNED_TARGET,
+    RESOLUTION_FOR_FIELDS_MAY_CHANGE,
+    RESOLUTION_FOR_METHODS_MAY_CHANGE,
+    STATIC_INITIALIZERS,
+    UNSAFE_INLINING,
+    UNSUPPORTED_ATTRIBUTES;
+
+    public void printLogMessageForClass(DexClass clazz) {
+      Log.info(VerticalClassMerger.class, getMessageForClass(clazz));
+    }
+
+    private String getMessageForClass(DexClass clazz) {
+      String message = null;
+      switch (this) {
+        case ALREADY_MERGED:
+          message = "it has already been merged with its superclass";
+          break;
+        case ALWAYS_INLINE:
+          message = "it is mentioned in appInfo.alwaysInline";
+          break;
+        case CONFLICT:
+          message = "it is conflicting with its subclass";
+          break;
+        case ILLEGAL_ACCESS:
+          message = "it could lead to illegal accesses";
+          break;
+        case NO_SIDE_EFFECTS:
+          message = "it is mentioned in appInfo.noSideEffects";
+          break;
+        case PINNED_SOURCE:
+          message = "it should be kept";
+          break;
+        case PINNED_TARGET:
+          message = "its target should be kept";
+          break;
+        case RESOLUTION_FOR_FIELDS_MAY_CHANGE:
+          message = "it could affect field resolution";
+          break;
+        case RESOLUTION_FOR_METHODS_MAY_CHANGE:
+          message = "it could affect method resolution";
+          break;
+        case STATIC_INITIALIZERS:
+          message = "merging of static initializers are not supported";
+          break;
+        case UNSAFE_INLINING:
+          message = "force-inlining might fail";
+          break;
+        case UNSUPPORTED_ATTRIBUTES:
+          message = "since inner-class attributes are not supported";
+          break;
+        default:
+          assert false;
+      }
+      return String.format("Cannot merge %s since %s.", clazz.toSourceString(), message);
+    }
+  }
+
   private final DexApplication application;
   private final AppInfoWithLiveness appInfo;
   private final GraphLense graphLense;
@@ -102,12 +167,12 @@
     // For all pinned fields, also pin the type of the field (because changing the type of the field
     // implicitly changes the signature of the field). Similarly, for all pinned methods, also pin
     // the return type and the parameter types of the method.
-    extractPinnedItems(appInfo.pinnedItems, pinnedTypes);
+    extractPinnedItems(appInfo.pinnedItems, pinnedTypes, AbortReason.PINNED_SOURCE);
 
     // TODO(christofferqa): Remove the invariant that the graph lense should not modify any
     // methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
-    extractPinnedItems(appInfo.alwaysInline, pinnedTypes);
-    extractPinnedItems(appInfo.noSideEffects.keySet(), pinnedTypes);
+    extractPinnedItems(appInfo.alwaysInline, pinnedTypes, AbortReason.ALWAYS_INLINE);
+    extractPinnedItems(appInfo.noSideEffects.keySet(), pinnedTypes, AbortReason.NO_SIDE_EFFECTS);
 
     for (DexProgramClass clazz : classes) {
       for (DexEncodedMethod method : clazz.methods()) {
@@ -145,40 +210,47 @@
     return pinnedTypes;
   }
 
-  private void extractPinnedItems(Iterable<DexItem> items, Set<DexType> pinnedTypes) {
+  private void extractPinnedItems(
+      Iterable<DexItem> items, Set<DexType> pinnedTypes, AbortReason reason) {
     for (DexItem item : items) {
       if (item instanceof DexType || item instanceof DexClass) {
         DexType type = item instanceof DexType ? (DexType) item : ((DexClass) item).type;
         // We check for the case where the type is pinned according to appInfo.isPinned, so only
         // add it here if it is not the case.
         if (!appInfo.isPinned(type)) {
-          markTypeAsPinned(type, pinnedTypes);
+          markTypeAsPinned(type, pinnedTypes, reason);
         }
       } else if (item instanceof DexField || item instanceof DexEncodedField) {
         // Pin the holder and the type of the field.
         DexField field =
             item instanceof DexField ? (DexField) item : ((DexEncodedField) item).field;
-        markTypeAsPinned(field.clazz, pinnedTypes);
-        markTypeAsPinned(field.type, pinnedTypes);
+        markTypeAsPinned(field.clazz, pinnedTypes, reason);
+        markTypeAsPinned(field.type, pinnedTypes, reason);
       } else if (item instanceof DexMethod || item instanceof DexEncodedMethod) {
         // Pin the holder, the return type and the parameter types of the method. If we were to
         // merge any of these types into their sub classes, then we would implicitly change the
         // signature of this method.
         DexMethod method =
             item instanceof DexMethod ? (DexMethod) item : ((DexEncodedMethod) item).method;
-        markTypeAsPinned(method.holder, pinnedTypes);
-        markTypeAsPinned(method.proto.returnType, pinnedTypes);
+        markTypeAsPinned(method.holder, pinnedTypes, reason);
+        markTypeAsPinned(method.proto.returnType, pinnedTypes, reason);
         for (DexType parameterType : method.proto.parameters.values) {
-          markTypeAsPinned(parameterType, pinnedTypes);
+          markTypeAsPinned(parameterType, pinnedTypes, reason);
         }
       }
     }
   }
 
-  private void markTypeAsPinned(DexType type, Set<DexType> pinnedTypes) {
+  private void markTypeAsPinned(DexType type, Set<DexType> pinnedTypes, AbortReason reason) {
     DexClass clazz = appInfo.definitionFor(type);
     if (clazz != null && clazz.isProgramClass()) {
-      pinnedTypes.add(type);
+      boolean changed = pinnedTypes.add(type);
+
+      if (Log.ENABLED) {
+        if (changed && isMergeCandidate(clazz.asProgramClass(), ImmutableSet.of())) {
+          reason.printLogMessageForClass(clazz);
+        }
+      }
     }
   }
 
@@ -188,6 +260,14 @@
         || pinnedTypes.contains(clazz.type)) {
       return false;
     }
+    if (mergedClassesInverse.containsKey(clazz.type)) {
+      // Do not allow merging the resulting class into its subclass.
+      // TODO(christofferqa): Get rid of this limitation.
+      if (Log.ENABLED) {
+        AbortReason.ALREADY_MERGED.printLogMessageForClass(clazz);
+      }
+      return false;
+    }
     DexType singleSubtype = clazz.type.getSingleSubtype();
     if (singleSubtype == null) {
       // TODO(christofferqa): Even if [clazz] has multiple subtypes, we could still merge it into
@@ -197,6 +277,9 @@
     }
     DexClass targetClass = appInfo.definitionFor(singleSubtype);
     if (mergeMayLeadToIllegalAccesses(clazz, targetClass)) {
+      if (Log.ENABLED) {
+        AbortReason.ILLEGAL_ACCESS.printLogMessageForClass(clazz);
+      }
       return false;
     }
     for (DexEncodedField field : clazz.fields()) {
@@ -210,6 +293,9 @@
       }
       if (method.isInstanceInitializer() && disallowInlining(method)) {
         // Cannot guarantee that markForceInline() will work.
+        if (Log.ENABLED) {
+          AbortReason.UNSAFE_INLINING.printLogMessageForClass(clazz);
+        }
         return false;
       }
     }
@@ -350,25 +436,30 @@
       assert !mergedClasses.containsKey(targetClass.type);
       if (appInfo.isPinned(targetClass.type)) {
         // We have to keep the target class intact, so we cannot merge it.
+        if (Log.ENABLED) {
+          AbortReason.PINNED_TARGET.printLogMessageForClass(clazz);
+        }
         continue;
       }
       if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
         // TODO(herhut): Handle class initializers.
         if (Log.ENABLED) {
-          Log.info(
-              getClass(),
-              "Cannot merge %s into %s due to static initializers.",
-              clazz.toSourceString(),
-              targetClass.toSourceString());
+          AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
         }
         continue;
       }
       if (methodResolutionMayChange(clazz, targetClass)) {
+        if (Log.ENABLED) {
+          AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE.printLogMessageForClass(clazz);
+        }
         continue;
       }
       // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
       // to the super class.
       if (fieldResolutionMayChange(clazz, targetClass)) {
+        if (Log.ENABLED) {
+          AbortReason.RESOLUTION_FOR_FIELDS_MAY_CHANGE.printLogMessageForClass(clazz);
+        }
         continue;
       }
       // Guard against the case where we have two methods that may get the same signature
@@ -376,11 +467,7 @@
       if (new CollisionDetector(clazz.type, targetClass.type, getInvokes(), mergedClasses)
           .mayCollide()) {
         if (Log.ENABLED) {
-          Log.info(
-              getClass(),
-              "Cannot merge %s into %s due to conflict.",
-              clazz.toSourceString(),
-              targetClass.toSourceString());
+          AbortReason.CONFLICT.printLogMessageForClass(clazz);
         }
         continue;
       }
@@ -389,9 +476,6 @@
       if (merged) {
         // Commit the changes to the graph lense.
         renamedMembersLense.merge(merger.getRenamings());
-        // Do not allow merging the resulting class into its subclass.
-        // TODO(christofferqa): Get rid of this limitation.
-        pinnedTypes.add(targetClass.type);
       }
       if (Log.ENABLED) {
         if (merged) {
@@ -507,6 +591,9 @@
       if (source.getEnclosingMethod() != null || !source.getInnerClasses().isEmpty()
           || target.getEnclosingMethod() != null || !target.getInnerClasses().isEmpty()) {
         // TODO(herhut): Consider supporting merging of inner-class attributes.
+        if (Log.ENABLED) {
+          AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(source);
+        }
         return false;
       }
       // Merge the class [clazz] into [targetClass] by adding all methods to