Top-down propagation of argument information to overrides

Bug: 190154391
Change-Id: I98b0c3d4edd0088765216b1cb432cab5e9c11a1d
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
index ca4b23c..12d1c4c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
@@ -60,7 +60,7 @@
   }
 
   public boolean isTrivial(TypeElement staticType) {
-    return staticType == getDynamicUpperBoundType();
+    return staticType == getDynamicUpperBoundType() || isUnknown();
   }
 
   public boolean isUnknown() {
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 b664811..750169d 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
@@ -15,6 +15,10 @@
     return BottomValue.getInstance();
   }
 
+  public static UnknownValue unknown() {
+    return UnknownValue.getInstance();
+  }
+
   public abstract boolean isNonTrivial();
 
   public boolean isSingleBoolean() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 83fdf6a..ed27028 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -9,12 +9,17 @@
 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.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -193,6 +198,68 @@
     return isTop ? CallSiteOptimizationInfo.top() : newCallSiteInfo;
   }
 
+  public static CallSiteOptimizationInfo fromMethodState(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod method,
+      ConcreteMonomorphicMethodState methodState) {
+    boolean allowConstantPropagation =
+        appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
+    ConcreteCallSiteOptimizationInfo newCallSiteInfo =
+        new ConcreteCallSiteOptimizationInfo(methodState.size(), allowConstantPropagation);
+    boolean isTop = true;
+    for (int argumentIndex = 0; argumentIndex < methodState.size(); argumentIndex++) {
+      ParameterState parameterState = methodState.getParameterState(argumentIndex);
+      if (parameterState.isUnknown()) {
+        continue;
+      }
+
+      ConcreteParameterState concreteParameterState = parameterState.asConcrete();
+
+      // Constant propagation.
+      if (allowConstantPropagation) {
+        AbstractValue abstractValue = concreteParameterState.getAbstractValue();
+        if (abstractValue.isNonTrivial()) {
+          newCallSiteInfo.constants.put(argumentIndex, abstractValue);
+          isTop = false;
+        }
+      }
+
+      // Type propagation.
+      DexType staticType = method.getDefinition().getArgumentType(argumentIndex);
+      if (staticType.isReferenceType()) {
+        TypeElement staticTypeElement = staticType.toTypeElement(appView);
+        if (staticType.isArrayType()) {
+          Nullability nullability = concreteParameterState.asArrayParameter().getNullability();
+          if (nullability.isDefinitelyNull()) {
+            if (allowConstantPropagation) {
+              newCallSiteInfo.constants.put(
+                  argumentIndex, appView.abstractValueFactory().createNullValue());
+            }
+          } else if (nullability.isDefinitelyNotNull()) {
+            newCallSiteInfo.dynamicUpperBoundTypes.put(
+                argumentIndex, staticTypeElement.asArrayType().asDefinitelyNotNull());
+          } else {
+            // Should never happen, since the parameter state is unknown in this case.
+            assert false;
+          }
+        } else if (staticType.isClassType()) {
+          DynamicType dynamicType =
+              method.getDefinition().isInstance() && argumentIndex == 0
+                  ? concreteParameterState.asReceiverParameter().getDynamicType()
+                  : concreteParameterState.asClassParameter().getDynamicType();
+          if (!dynamicType.isTrivial(staticTypeElement)) {
+            newCallSiteInfo.dynamicUpperBoundTypes.put(
+                argumentIndex, dynamicType.getDynamicUpperBoundType());
+            isTop = false;
+          } else {
+            newCallSiteInfo.dynamicUpperBoundTypes.put(argumentIndex, staticTypeElement);
+          }
+        }
+      }
+    }
+    return isTop ? CallSiteOptimizationInfo.top() : newCallSiteInfo;
+  }
+
   @Override
   public boolean isConcreteCallSiteOptimizationInfo() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java
new file mode 100644
index 0000000..fea97a2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.Supplier;
+
+public class BottomMethodState extends MethodStateBase {
+
+  private static final BottomMethodState INSTANCE = new BottomMethodState();
+
+  private BottomMethodState() {}
+
+  public static BottomMethodState get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isBottom() {
+    return true;
+  }
+
+  @Override
+  public MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState) {
+    return methodState;
+  }
+
+  @Override
+  public MethodState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) {
+    return methodStateSupplier.get();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
index e66cea9..7615aaf 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
 import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 
 public class ConcreteArrayTypeParameterState extends ConcreteParameterState {
 
@@ -31,10 +32,19 @@
   }
 
   @Override
+  public AbstractValue getAbstractValue() {
+    return AbstractValue.unknown();
+  }
+
+  @Override
   public ConcreteParameterStateKind getKind() {
     return ConcreteParameterStateKind.ARRAY;
   }
 
+  public Nullability getNullability() {
+    return nullability;
+  }
+
   @Override
   public boolean isArrayParameter() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
index 03fc610..9574d08 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
@@ -51,6 +51,15 @@
   }
 
   @Override
+  public AbstractValue getAbstractValue() {
+    return abstractValue;
+  }
+
+  public DynamicType getDynamicType() {
+    return dynamicType;
+  }
+
+  @Override
   public ConcreteParameterStateKind getKind() {
     return ConcreteParameterStateKind.CLASS;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java
index 146d321..6aae75f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java
@@ -11,6 +11,11 @@
 public abstract class ConcreteMethodState extends MethodStateBase {
 
   @Override
+  public boolean isConcrete() {
+    return true;
+  }
+
+  @Override
   public ConcreteMethodState asConcrete() {
     return this;
   }
@@ -24,15 +29,22 @@
   }
 
   @Override
-  public MethodState mutableJoin(
-      AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) {
-    MethodState methodState = methodStateSupplier.get();
+  public MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState) {
+    if (methodState.isBottom()) {
+      return this;
+    }
     if (methodState.isUnknown()) {
       return methodState;
     }
     return mutableJoin(appView, methodState.asConcrete());
   }
 
+  @Override
+  public MethodState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) {
+    return mutableJoin(appView, methodStateSupplier.get());
+  }
+
   private MethodState mutableJoin(
       AppView<AppInfoWithLiveness> appView, ConcreteMethodState methodState) {
     if (isMonomorphic() && methodState.isMonomorphic()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
index 99609ac..c93f4cf 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
@@ -20,6 +20,10 @@
     this.parameterStates = parameterStates;
   }
 
+  public ParameterState getParameterState(int index) {
+    return parameterStates.get(index);
+  }
+
   public ConcreteMonomorphicMethodStateOrUnknown mutableJoin(
       AppView<AppInfoWithLiveness> appView, ConcreteMonomorphicMethodState methodState) {
     if (size() != methodState.size()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java
index bcb3d28..2e5a908 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.BiConsumer;
 
 public class ConcretePolymorphicMethodState extends ConcreteMethodState {
 
@@ -22,6 +23,10 @@
     receiverBoundsToState.put(receiverBounds, methodState);
   }
 
+  public void forEach(BiConsumer<DynamicType, MethodState> consumer) {
+    receiverBoundsToState.forEach(consumer);
+  }
+
   public boolean isEmpty() {
     return receiverBoundsToState.isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
index a10d24b..dc50932 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
@@ -43,6 +43,11 @@
   }
 
   @Override
+  public AbstractValue getAbstractValue() {
+    return abstractValue;
+  }
+
+  @Override
   public ConcreteParameterStateKind getKind() {
     return ConcreteParameterStateKind.PRIMITIVE;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java
index 2af7b04..9a529b2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
 import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 
 public class ConcreteReceiverParameterState extends ConcreteParameterState {
 
@@ -31,6 +32,15 @@
   }
 
   @Override
+  public AbstractValue getAbstractValue() {
+    return AbstractValue.unknown();
+  }
+
+  public DynamicType getDynamicType() {
+    return dynamicType;
+  }
+
+  @Override
   public ConcreteParameterStateKind getKind() {
     return ConcreteParameterStateKind.RECEIVER;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java
index ef56eed..5bc6049 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java
@@ -10,10 +10,16 @@
 
 public interface MethodState {
 
+  static BottomMethodState bottom() {
+    return BottomMethodState.get();
+  }
+
   static UnknownMethodState unknown() {
     return UnknownMethodState.get();
   }
 
+  boolean isBottom();
+
   boolean isConcrete();
 
   ConcreteMethodState asConcrete();
@@ -24,6 +30,8 @@
 
   boolean isUnknown();
 
+  MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState);
+
   MethodState mutableJoin(
       AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier);
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java
index d102f6a..936210f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java
@@ -6,11 +6,20 @@
 
 public abstract class MethodStateBase implements MethodState {
 
+  public static BottomMethodState bottom() {
+    return BottomMethodState.get();
+  }
+
   public static UnknownMethodState unknown() {
     return UnknownMethodState.get();
   }
 
   @Override
+  public boolean isBottom() {
+    return false;
+  }
+
+  @Override
   public boolean isConcrete() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
index f8eacd3..10e9573 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
@@ -30,6 +30,22 @@
     return new MethodStateCollection(new ConcurrentHashMap<>());
   }
 
+  public void addMethodState(
+      AppView<AppInfoWithLiveness> appView, DexMethod method, MethodState methodState) {
+    if (methodState.isUnknown()) {
+      methodStates.put(method, methodState);
+    } else {
+      methodStates.compute(
+          method,
+          (ignore, existingMethodState) -> {
+            if (existingMethodState == null) {
+              return methodState;
+            }
+            return existingMethodState.mutableJoin(appView, methodState);
+          });
+    }
+  }
+
   /**
    * This intentionally takes a {@link Supplier<MethodState>} to avoid computing the method state
    * for a given call site when nothing is known about the arguments of the method.
@@ -58,6 +74,11 @@
   }
 
   public MethodState get(ProgramMethod method) {
-    return methodStates.get(method.getReference());
+    return methodStates.getOrDefault(method.getReference(), MethodState.bottom());
+  }
+
+  public MethodState remove(ProgramMethod method) {
+    MethodState removed = methodStates.remove(method.getReference());
+    return removed != null ? removed : MethodState.bottom();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java
index 30953db..8f759cf 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public abstract class ParameterState {
@@ -13,6 +14,8 @@
     return UnknownParameterState.get();
   }
 
+  public abstract AbstractValue getAbstractValue();
+
   public boolean isConcrete() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java
index 815a97d..1cfe728 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java
@@ -26,6 +26,11 @@
   }
 
   @Override
+  public MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState) {
+    return this;
+  }
+
+  @Override
   public MethodState mutableJoin(
       AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) {
     return this;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java
index 5bd3156..8993b5c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class UnknownParameterState extends ParameterState {
@@ -18,6 +19,11 @@
   }
 
   @Override
+  public AbstractValue getAbstractValue() {
+    return AbstractValue.unknown();
+  }
+
+  @Override
   public boolean isUnknown() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
index 844e000..a1ac738 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -103,7 +103,7 @@
     interfaceDefinition.forEachProgramVirtualMethod(
         method -> {
           MethodState methodState = methodStates.get(method);
-          if (methodState == null) {
+          if (methodState.isBottom()) {
             return;
           }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
index a455566..0e51d66 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -5,18 +5,124 @@
 package com.android.tools.r8.optimize.argumentpropagation.propagation;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePolymorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 public class VirtualDispatchMethodArgumentPropagator extends MethodArgumentPropagator {
 
+  class PropagationState {
+
+    // Argument information for virtual methods that must be propagated to all overrides (i.e., this
+    // information does not have a lower bound).
+    final MethodStateCollection active = MethodStateCollection.create();
+
+    // Argument information for virtual methods that must be propagated to all overrides that are
+    // above the given lower bound.
+    final Map<DexType, MethodStateCollection> activeUntilLowerBound = new IdentityHashMap<>();
+
+    // Argument information for virtual methods that is currently inactive, but should be propagated
+    // to all overrides below a given upper bound.
+    final Map<DynamicType, MethodStateCollection> inactiveUntilUpperBound = new HashMap<>();
+
+    PropagationState(DexProgramClass clazz) {
+      // Join the argument information from each of the super types.
+      immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+          clazz,
+          (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
+          (supertype, superclass) -> addParentState(clazz, superclass.asProgramClass()));
+    }
+
+    // TODO(b/190154391): This currently copies the state of the superclass into its immediate
+    //  given subclass. Instead of copying the state, consider linking the states. This would reduce
+    //  memory usage, but would require visiting all transitive (program) super classes for each
+    //  subclass.
+    private void addParentState(DexProgramClass clazz, DexProgramClass superclass) {
+      PropagationState parentState = propagationStates.get(superclass.asProgramClass());
+      assert parentState != null;
+
+      // Add the argument information that must be propagated to all method overrides.
+      active.addMethodStates(appView, parentState.active);
+
+      // Add the argument information that is active until a given lower bound.
+      parentState.activeUntilLowerBound.forEach(
+          (lowerBound, activeMethodState) -> {
+            if (lowerBound != superclass.getType()) {
+              // TODO(b/190154391): Verify that the lower bound is a subtype of the current.
+              //  Otherwise we carry this information to all subtypes although there is no need to.
+              activeUntilLowerBound
+                  .computeIfAbsent(lowerBound, ignoreKey(MethodStateCollection::create))
+                  .addMethodStates(appView, activeMethodState);
+            } else {
+              // No longer active.
+            }
+          });
+
+      // Add the argument information that is inactive until a given upper bound.
+      parentState.inactiveUntilUpperBound.forEach(
+          (bounds, inactiveMethodState) -> {
+            ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
+            if (upperBound.getClassType() == clazz.getType()) {
+              // The upper bound is the current class, thus this inactive information now becomes
+              // active.
+              if (bounds.hasDynamicLowerBoundType()) {
+                activeUntilLowerBound
+                    .computeIfAbsent(
+                        bounds.getDynamicLowerBoundType().getClassType(),
+                        ignoreKey(MethodStateCollection::create))
+                    .addMethodStates(appView, inactiveMethodState);
+              } else {
+                active.addMethodStates(appView, inactiveMethodState);
+              }
+            } else {
+              // Still inactive.
+              // TODO(b/190154391): Only carry this information downwards if the upper bound is a
+              //  subtype of this class. Otherwise we carry this information to all subtypes,
+              //  although clearly the information will never become active.
+              inactiveUntilUpperBound
+                  .computeIfAbsent(bounds, ignoreKey(MethodStateCollection::create))
+                  .addMethodStates(appView, inactiveMethodState);
+            }
+          });
+    }
+
+    private MethodState computeMethodStateForPolymorhicMethod(ProgramMethod method) {
+      assert method.getDefinition().isNonPrivateVirtualMethod();
+      MethodState methodState = active.get(method);
+      for (MethodStateCollection methodStates : activeUntilLowerBound.values()) {
+        methodState = methodState.mutableJoin(appView, methodStates.get(method));
+      }
+      return methodState;
+    }
+  }
+
+  // For each class, stores the argument information for each virtual method on this class and all
+  // direct and indirect super classes.
+  //
+  // This data structure is populated during a top-down traversal over the class hierarchy, such
+  // that entries in the map can be removed when the top-down traversal has visited all subtypes of
+  // a given node.
+  final Map<DexProgramClass, PropagationState> propagationStates = new IdentityHashMap<>();
+
   public VirtualDispatchMethodArgumentPropagator(
       AppView<AppInfoWithLiveness> appView,
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
@@ -25,6 +131,13 @@
   }
 
   @Override
+  public void run(Set<DexProgramClass> stronglyConnectedComponent) {
+    super.run(stronglyConnectedComponent);
+    assert verifyAllClassesFinished(stronglyConnectedComponent);
+    assert verifyStatePruned(stronglyConnectedComponent);
+  }
+
+  @Override
   public void forEachSubClass(DexProgramClass clazz, Consumer<DexProgramClass> consumer) {
     immediateSubtypingInfo.getSubclasses(clazz).forEach(consumer);
   }
@@ -47,11 +160,137 @@
 
   @Override
   public void visit(DexProgramClass clazz) {
-    throw new Unimplemented();
+    assert !propagationStates.containsKey(clazz);
+    PropagationState propagationState = computePropagationState(clazz);
+    setOptimizationInfo(clazz, propagationState);
+  }
+
+  private PropagationState computePropagationState(DexProgramClass clazz) {
+    PropagationState propagationState = new PropagationState(clazz);
+
+    // Join the argument information from the methods of the current class.
+    clazz.forEachProgramVirtualMethod(
+        method -> {
+          MethodState methodState = methodStates.get(method);
+          if (methodState.isBottom()) {
+            return;
+          }
+
+          // TODO(b/190154391): Add an unknown polymorphic method state, such that we can
+          //  distinguish monomorphic unknown method states from polymorphic unknown method states.
+          //  We only need to propagate polymorphic unknown method states here.
+          if (methodState.isUnknown()) {
+            propagationState.active.addMethodState(appView, method.getReference(), methodState);
+            return;
+          }
+
+          ConcreteMethodState concreteMethodState = methodState.asConcrete();
+          if (concreteMethodState.isMonomorphic()) {
+            // No need to propagate information for methods that do not override other methods and
+            // are not themselves overridden.
+            return;
+          }
+
+          ConcretePolymorphicMethodState polymorphicMethodState =
+              concreteMethodState.asPolymorphic();
+          polymorphicMethodState.forEach(
+              (bounds, methodStateForBounds) -> {
+                if (bounds.isUnknown()) {
+                  propagationState.active.addMethodState(
+                      appView, method.getReference(), methodStateForBounds);
+                } else {
+                  // TODO(b/190154391): Verify that the bounds are not trivial according to the
+                  //  static receiver type.
+                  ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
+                  if (upperBound.getClassType() == clazz.getType()) {
+                    if (bounds.hasDynamicLowerBoundType()) {
+                      // TODO(b/190154391): Verify that the lower bound is a subtype of the current
+                      //  class.
+                      propagationState
+                          .activeUntilLowerBound
+                          .computeIfAbsent(
+                              bounds.getDynamicLowerBoundType().getClassType(),
+                              ignoreKey(MethodStateCollection::create))
+                          .addMethodState(appView, method.getReference(), methodStateForBounds);
+                    } else {
+                      propagationState.active.addMethodState(
+                          appView, method.getReference(), methodStateForBounds);
+                    }
+                  } else {
+                    assert !appView.appInfo().isSubtype(clazz.getType(), upperBound.getClassType());
+                    propagationState
+                        .inactiveUntilUpperBound
+                        .computeIfAbsent(bounds, ignoreKey(MethodStateCollection::create))
+                        .addMethodState(appView, method.getReference(), methodStateForBounds);
+                  }
+                }
+              });
+        });
+
+    propagationStates.put(clazz, propagationState);
+    return propagationState;
+  }
+
+  private void setOptimizationInfo(DexProgramClass clazz, PropagationState propagationState) {
+    clazz.forEachProgramMethod(method -> setOptimizationInfo(method, propagationState));
+  }
+
+  private void setOptimizationInfo(ProgramMethod method, PropagationState propagationState) {
+    MethodState methodState = methodStates.remove(method);
+
+    // If this is a polymorphic method, we need to compute the method state to account for dynamic
+    // dispatch.
+    if (methodState.isConcrete() && methodState.asConcrete().isPolymorphic()) {
+      methodState = propagationState.computeMethodStateForPolymorhicMethod(method);
+    }
+
+    if (methodState.isBottom()) {
+      // TODO(b/190154391): This should only happen if the method is never called. Consider removing
+      //  the method in this case.
+      return;
+    }
+
+    if (methodState.isUnknown()) {
+      // Nothing is known about the arguments to this method.
+      return;
+    }
+
+    ConcreteMethodState concreteMethodState = methodState.asConcrete();
+    if (concreteMethodState.isPolymorphic()) {
+      assert false;
+      return;
+    }
+
+    // TODO(b/190154391): We need to resolve the flow constraints before this is guaranteed to be
+    //  sound.
+    method
+        .getDefinition()
+        .joinCallSiteOptimizationInfo(
+            ConcreteCallSiteOptimizationInfo.fromMethodState(
+                appView, method, concreteMethodState.asMonomorphic()),
+            appView);
   }
 
   @Override
   public void prune(DexProgramClass clazz) {
-    throw new Unimplemented();
+    propagationStates.remove(clazz);
+  }
+
+  private boolean verifyAllClassesFinished(Set<DexProgramClass> stronglyConnectedComponent) {
+    for (DexProgramClass clazz : stronglyConnectedComponent) {
+      assert isClassFinished(clazz);
+    }
+    return true;
+  }
+
+  private boolean verifyStatePruned(Set<DexProgramClass> stronglyConnectedComponent) {
+    Set<DexType> types =
+        stronglyConnectedComponent.stream().map(DexClass::getType).collect(Collectors.toSet());
+    methodStates.forEach(
+        (method, methodState) -> {
+          assert !types.contains(method.getHolderType());
+        });
+    assert propagationStates.isEmpty();
+    return true;
   }
 }