Strengthen top-down propagation of argument information to virtual methods

Change-Id: If5b1ce3f2600e981aba7668842eb4201a54375c2
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index b73ba99..aa805a6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.code.AbstractValueSupplier;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -243,14 +244,16 @@
             stronglyConnectedProgramComponents,
             interfaceDispatchOutsideProgram)
         .propagateOptimizationInfo(executorService, timing);
+
     // TODO(b/296030319): Also publish the computed optimization information for fields.
-    new ArgumentPropagatorOptimizationInfoPopulator(
-            appView, converter, fieldStates, methodStates, postMethodProcessorBuilder)
-        .populateOptimizationInfo(executorService, timing);
+    PrunedItems prunedItems =
+        new ArgumentPropagatorOptimizationInfoPopulator(
+                appView, converter, fieldStates, methodStates, postMethodProcessorBuilder)
+            .populateOptimizationInfo(executorService, timing);
     timing.end();
 
     timing.begin("Compute unused arguments");
-    effectivelyUnusedArgumentsAnalysis.computeEffectivelyUnusedArguments();
+    effectivelyUnusedArgumentsAnalysis.computeEffectivelyUnusedArguments(prunedItems);
     effectivelyUnusedArgumentsAnalysis = null;
     timing.end();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index 99249e3..d830831 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -32,6 +33,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -71,17 +73,19 @@
    * Computes an over-approximation of each parameter's value and type and stores the result in
    * {@link com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}.
    */
-  void populateOptimizationInfo(ExecutorService executorService, Timing timing)
+  PrunedItems populateOptimizationInfo(ExecutorService executorService, Timing timing)
       throws ExecutionException {
     // The information stored on each method is now sound, and can be used as optimization info.
     timing.begin("Set optimization info");
-    setOptimizationInfo(executorService);
+    PrunedItems prunedItems = setOptimizationInfo(executorService);
     timing.end();
 
     assert methodStates.isEmpty();
+    return prunedItems;
   }
 
-  private void setOptimizationInfo(ExecutorService executorService) throws ExecutionException {
+  private PrunedItems setOptimizationInfo(ExecutorService executorService)
+      throws ExecutionException {
     ProgramMethodSet prunedMethods = ProgramMethodSet.createConcurrent();
     ThreadUtils.processItems(
         appView.appInfo().classes(),
@@ -94,6 +98,10 @@
     }
     converter.pruneItems(executorService);
     converter.waveDone(ProgramMethodSet.empty(), executorService);
+    return PrunedItems.builder()
+        .setPrunedApp(appView.app())
+        .setRemovedMethods(prunedMethods.toReferenceSet(SetUtils::newIdentityHashSet))
+        .build();
   }
 
   private ProgramMethodSet setOptimizationInfo(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
index 0165f8e..c380eab 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
@@ -125,6 +125,10 @@
     boolean isMayDispatchOutsideProgramSet() {
       return mayDispatchOutsideProgram;
     }
+
+    boolean isOverriddenBy(VirtualRootMethod other) {
+      return overrides.contains(other);
+    }
   }
 
   private final Map<DexProgramClass, DexMethodSignatureMap<VirtualRootMethod>>
@@ -167,9 +171,35 @@
                         // forEachImmediateProgramSuperClass. Therefore, the current method is
                         // guaranteed to be an interface method when existing != null.
                         assert info.getMethod().getHolder().isInterface();
-                        if (!existing.getMethod().getHolder().isInterface()) {
+                        // Add the existing as a sibling of the current method (or vice versa).
+                        // Despite the fact that these two methods come from two different
+                        // extends/implements edges, the two methods may already be related by
+                        // overriding, as in the following example.
+                        //
+                        //   interface I { void m(); }
+                        //   interface J extends I { @Override default void m() { ... } }
+                        //   abstract class A implements I {}
+                        //   class B extends A implements J {}
+                        //
+                        // When processing the extends edge B->A, we will pull down the definition
+                        // of I.m(). Next, when processing the implements edge B->J we will pull
+                        // down the definition of J.m(). Since J.m() is an override of I.m() we
+                        // should avoid marking the two methods as siblings.
+                        if (info.isOverriddenBy(existing)) {
+                          return existing;
+                        }
+                        if (existing.isOverriddenBy(info)) {
+                          return info;
+                        }
+                        if (!existing.isAbstract()) {
                           existing.addSibling(info);
                           info.addOverride(existing);
+                          return existing;
+                        }
+                        if (existing.getMethod().getHolder().isInterface() && !info.isAbstract()) {
+                          info.addSibling(existing);
+                          existing.addOverride(info);
+                          return info;
                         }
                         return existing;
                       }
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 5fcd8db..6bc6ad0 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
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethodSignature;
 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.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -154,7 +155,7 @@
 
                   MethodState transformedInterfaceMethodState =
                       transformInterfaceMethodStateForClassMethod(
-                          subclass, resolvedMethod, interfaceMethodState);
+                          appView, subclass, resolvedMethod, interfaceMethodState, methodStates);
                   if (!transformedInterfaceMethodState.isBottom()) {
                     methodStates.addMethodState(
                         appView, resolvedMethod, transformedInterfaceMethodState);
@@ -162,12 +163,23 @@
                 }));
   }
 
-  private MethodState transformInterfaceMethodStateForClassMethod(
-      DexProgramClass clazz, ProgramMethod resolvedMethod, MethodState methodState) {
-    if (!methodState.isPolymorphic()) {
-      return methodState.mutableCopy();
+  public static MethodState transformInterfaceMethodStateForClassMethod(
+      AppView<AppInfoWithLiveness> appView,
+      DexProgramClass clazz,
+      ProgramMethod resolvedMethod,
+      MethodState methodState,
+      MethodStateCollectionByReference methodStates) {
+    if (methodState.isBottom() || methodState.isUnknown()) {
+      return methodState;
     }
 
+    if (methodState.isMonomorphic()) {
+      assert false;
+      return MethodState.bottom();
+    }
+
+    assert methodState.isPolymorphic();
+
     // Rewrite the bounds of the polymorphic method state. If a given piece of argument information
     // should be propagated to the resolved method, we replace the type bounds by the holder of the
     // resolved method.
@@ -177,7 +189,16 @@
             appView,
             bounds -> {
               boolean shouldPropagateMethodStateForBounds;
-              if (bounds.isUnknown()) {
+              if (bounds.isExactClassType()) {
+                ClassTypeElement exactClassType = bounds.getExactClassType();
+                DexType exactType =
+                    exactClassType.getClassType().isIdenticalTo(appView.dexItemFactory().objectType)
+                            && exactClassType.getInterfaces().hasSingleKnownInterface()
+                        ? exactClassType.getInterfaces().getSingleKnownInterface()
+                        : exactClassType.getClassType();
+                shouldPropagateMethodStateForBounds =
+                    exactType.isIdenticalTo(resolvedMethod.getHolderType());
+              } else if (bounds.isUnknown()) {
                 shouldPropagateMethodStateForBounds = true;
               } else {
                 ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
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 4b3349c..305d88d 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,6 +5,7 @@
 package com.android.tools.r8.optimize.argumentpropagation.propagation;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
@@ -16,15 +17,19 @@
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 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.analysis.type.DynamicTypeWithUpperBound;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePolymorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReceiverValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionBySignature;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collection;
 import java.util.HashMap;
@@ -70,12 +75,10 @@
       // 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(MethodStateCollectionBySignature::create))
-                  .addMethodStates(appView, activeMethodState);
+            TypeElement lowerBoundType = lowerBound.toTypeElement(appView);
+            TypeElement currentType = clazz.getType().toTypeElement(appView);
+            if (lowerBoundType.lessThanOrEqual(currentType, appView)) {
+              addActiveUntilLowerBound(lowerBound, activeMethodState);
             } else {
               // No longer active.
             }
@@ -85,68 +88,107 @@
       parentState.inactiveUntilUpperBound.forEach(
           (bounds, inactiveMethodStates) -> {
             ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
-            if (shouldActivateMethodStateGuardedByBounds(upperBound, clazz, superclass)) {
-              // The upper bound is the current class, thus this inactive information now becomes
-              // active.
-              if (bounds.hasDynamicLowerBoundType()) {
-                activeUntilLowerBound
-                    .computeIfAbsent(
-                        bounds.getDynamicLowerBoundType().getClassType(),
-                        ignoreKey(MethodStateCollectionBySignature::create))
-                    .addMethodStates(appView, inactiveMethodStates);
-              } else {
-                active.addMethodStates(appView, inactiveMethodStates);
-              }
-
-              inactiveMethodStates.forEach(
-                  (signature, methodState) -> {
-                    SingleResolutionResult<?> resolutionResult =
-                        appView
-                            .appInfo()
-                            .resolveMethodOnLegacy(clazz, signature)
-                            .asSingleResolution();
-
-                    // Find the first virtual method in the super class hierarchy.
-                    while (resolutionResult != null
-                        && resolutionResult.getResolvedMethod().belongsToDirectPool()) {
-                      resolutionResult =
-                          appView
-                              .appInfo()
-                              .resolveMethodOnClassLegacy(
-                                  resolutionResult.getResolvedHolder().getSuperType(), signature)
-                              .asSingleResolution();
-                    }
-
-                    // Propagate the argument information to the method on the super class.
-                    if (resolutionResult != null
-                        && resolutionResult.getResolvedHolder().isProgramClass()
-                        && resolutionResult.getResolvedHolder() != clazz
-                        && resolutionResult.getResolvedMethod().hasCode()) {
-                      DexProgramClass resolvedHolder =
-                          resolutionResult.getResolvedHolder().asProgramClass();
-                      propagationStates
-                          .get(resolvedHolder)
-                          .activeUntilLowerBound
-                          .computeIfAbsent(
-                              resolvedHolder.getType(),
-                              ignoreKey(MethodStateCollectionBySignature::create))
-                          .addMethodState(
-                              appView, resolutionResult.getResolvedProgramMethod(), methodState);
-                    }
-                  });
-            } else {
+            if (!shouldActivateMethodStateGuardedByBounds(upperBound, clazz, superclass)) {
               // 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(MethodStateCollectionBySignature::create))
-                  .addMethodStates(appView, inactiveMethodStates);
+              addInactiveUntilUpperBound(bounds, inactiveMethodStates);
+              return;
             }
+
+            // The upper bound is the current class, thus this inactive information now becomes
+            // active.
+            if (bounds.hasDynamicLowerBoundType()) {
+              // For class methods with sibling interface methods, we can have lower bound type
+              // information on the sibling interface method. When this information is propagated
+              // down to the common subtype, then there is no need to propagate the information
+              // any further, since the common subtype is already below the lower bound.
+              //
+              // Note that this does not imply that the information stored on the sibling
+              // interface method is not applied. The information is propagated to the class
+              // method that implements the interface method below.
+              ClassTypeElement lowerBound = bounds.getDynamicLowerBoundType();
+              TypeElement currentType = clazz.getType().toTypeElement(appView);
+              if (lowerBound.lessThanOrEqual(currentType, appView)) {
+                DexType activeUntilLowerBoundType =
+                    lowerBound.getClassType().isIdenticalTo(appView.dexItemFactory().objectType)
+                            && lowerBound.getInterfaces().hasSingleKnownInterface()
+                        ? lowerBound.getInterfaces().getSingleKnownInterface()
+                        : lowerBound.getClassType();
+                addActiveUntilLowerBound(activeUntilLowerBoundType, inactiveMethodStates);
+              } else {
+                return;
+              }
+            } else {
+              active.addMethodStates(appView, inactiveMethodStates);
+            }
+
+            inactiveMethodStates.forEach(
+                (signature, methodState) -> {
+                  SingleResolutionResult<?> resolutionResult =
+                      appView
+                          .appInfo()
+                          .resolveMethodOnLegacy(clazz, signature)
+                          .asSingleResolution();
+
+                  // Find the first virtual method in the super class hierarchy.
+                  while (resolutionResult != null
+                      && resolutionResult.getResolvedMethod().belongsToDirectPool()) {
+                    resolutionResult =
+                        appView
+                            .appInfo()
+                            .resolveMethodOnClassLegacy(
+                                resolutionResult.getResolvedHolder().getSuperType(), signature)
+                            .asSingleResolution();
+                  }
+
+                  // Propagate the argument information to the method on the super class.
+                  if (resolutionResult != null
+                      && resolutionResult.getResolvedHolder().isProgramClass()
+                      && resolutionResult.getResolvedHolder() != clazz
+                      && resolutionResult.getResolvedMethod().hasCode()) {
+                    DexProgramClass resolvedHolder =
+                        resolutionResult.getResolvedHolder().asProgramClass();
+                    PropagationState propagationState = propagationStates.get(resolvedHolder);
+                    propagationState.addActiveUntilLowerBound(
+                        resolvedHolder.getType(),
+                        resolutionResult.getResolvedProgramMethod(),
+                        methodState);
+                  }
+                });
           });
     }
 
-    private MethodState computeMethodStateForPolymorhicMethod(ProgramMethod method) {
+    private void addActiveUntilLowerBound(
+        DexType lowerBound, ProgramMethod method, MethodState methodState) {
+      activeUntilLowerBound
+          .computeIfAbsent(lowerBound, ignoreKey(MethodStateCollectionBySignature::create))
+          .addMethodState(appView, method, methodState);
+    }
+
+    private void addActiveUntilLowerBound(
+        DexType lowerBound, MethodStateCollectionBySignature methodStates) {
+      activeUntilLowerBound
+          .computeIfAbsent(lowerBound, ignoreKey(MethodStateCollectionBySignature::create))
+          .addMethodStates(appView, methodStates);
+    }
+
+    private void addInactiveUntilUpperBound(
+        DynamicTypeWithUpperBound upperBound, ProgramMethod method, MethodState methodState) {
+      inactiveUntilUpperBound
+          .computeIfAbsent(upperBound, ignoreKey(MethodStateCollectionBySignature::create))
+          .addMethodState(appView, method, methodState);
+    }
+
+    private void addInactiveUntilUpperBound(
+        DynamicTypeWithUpperBound upperBound, MethodStateCollectionBySignature methodStates) {
+      inactiveUntilUpperBound
+          .computeIfAbsent(upperBound, ignoreKey(MethodStateCollectionBySignature::create))
+          .addMethodStates(appView, methodStates);
+    }
+
+    private MethodState computeMethodStateForPolymorphicMethod(ProgramMethod method) {
       assert method.getDefinition().isNonPrivateVirtualMethod();
       MethodState methodState = active.get(method).mutableCopy();
       if (!activeUntilLowerBound.isEmpty()) {
@@ -157,9 +199,56 @@
                   appView, methodSignature, methodStates.get(method), StateCloner.getCloner());
         }
       }
+      if (methodState.isMonomorphic()) {
+        ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
+        ValueState receiverState = monomorphicMethodState.getParameterState(0);
+        if (receiverState.isReceiverState()) {
+          ConcreteReceiverValueState concreteReceiverState = receiverState.asReceiverState();
+          DynamicType dynamicType = concreteReceiverState.getDynamicType();
+          DynamicType refinedDynamicType = computeRefinedReceiverDynamicType(method, dynamicType);
+          if (!refinedDynamicType.equals(dynamicType)) {
+            monomorphicMethodState.setParameterState(
+                0,
+                refinedDynamicType.isNotNullType()
+                    ? ValueState.unknown()
+                    : new ConcreteReceiverValueState(
+                        refinedDynamicType, concreteReceiverState.copyInFlow()));
+          }
+        } else {
+          assert receiverState.isBottom() || receiverState.isUnknown();
+        }
+      }
       return methodState;
     }
 
+    private DynamicType computeRefinedReceiverDynamicType(
+        ProgramMethod method, DynamicType dynamicType) {
+      if (!dynamicType.isDynamicTypeWithUpperBound()) {
+        return dynamicType;
+      }
+      DynamicTypeWithUpperBound dynamicTypeWithUpperBound =
+          dynamicType.asDynamicTypeWithUpperBound();
+      TypeElement dynamicUpperBoundType = dynamicTypeWithUpperBound.getDynamicUpperBoundType();
+      TypeElement staticUpperBoundType =
+          method.getHolderType().toTypeElement(appView, definitelyNotNull());
+      if (dynamicUpperBoundType.lessThanOrEqualUpToNullability(staticUpperBoundType, appView)) {
+        DynamicType newDynamicType = dynamicType.withNullability(definitelyNotNull());
+        assert newDynamicType.equals(dynamicType)
+            || !dynamicType.getNullability().isDefinitelyNotNull();
+        return newDynamicType;
+      }
+      ClassTypeElement dynamicLowerBoundType = dynamicTypeWithUpperBound.getDynamicLowerBoundType();
+      if (dynamicLowerBoundType == null) {
+        return DynamicType.definitelyNotNull();
+      }
+      assert dynamicLowerBoundType.lessThanOrEqualUpToNullability(staticUpperBoundType, appView);
+      if (dynamicLowerBoundType.equalUpToNullability(staticUpperBoundType)) {
+        return DynamicType.createExact(dynamicLowerBoundType.asDefinitelyNotNull());
+      }
+      return DynamicType.create(
+          appView, staticUpperBoundType, dynamicLowerBoundType.asDefinitelyNotNull());
+    }
+
     @SuppressWarnings("ReferenceEquality")
     private boolean shouldActivateMethodStateGuardedByBounds(
         ClassTypeElement upperBound, DexProgramClass currentClass, DexProgramClass superClass) {
@@ -181,6 +270,15 @@
       // upper bound class type and the upper bound interface types.
       return classType.lessThanOrEqualUpToNullability(upperBound, appView);
     }
+
+    boolean verifyActiveUntilLowerBoundRelevance(DexProgramClass clazz) {
+      TypeElement currentType = clazz.getType().toTypeElement(appView);
+      for (DexType lowerBound : activeUntilLowerBound.keySet()) {
+        TypeElement lowerBoundType = lowerBound.toTypeElement(appView);
+        assert lowerBoundType.lessThanOrEqual(currentType, appView);
+      }
+      return true;
+    }
   }
 
   // For each class, stores the argument information for each virtual method on this class and all
@@ -251,12 +349,18 @@
                     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(MethodStateCollectionBySignature::create))
-                          .addMethodState(appView, method, methodStateForBounds);
+                      ClassTypeElement lowerBound = bounds.getDynamicLowerBoundType();
+                      DexType activeUntilLowerBoundType =
+                          lowerBound
+                                      .getClassType()
+                                      .isIdenticalTo(appView.dexItemFactory().objectType)
+                                  && lowerBound.getInterfaces().hasSingleKnownInterface()
+                              ? lowerBound.getInterfaces().getSingleKnownInterface()
+                              : lowerBound.getClassType();
+                      assert !bounds.isExactClassType()
+                          || activeUntilLowerBoundType.isIdenticalTo(clazz.getType());
+                      propagationState.addActiveUntilLowerBound(
+                          activeUntilLowerBoundType, method, methodStateForBounds);
                     } else {
                       propagationState.active.addMethodState(appView, method, methodStateForBounds);
                     }
@@ -265,23 +369,20 @@
                         .getType()
                         .toTypeElement(appView)
                         .lessThanOrEqualUpToNullability(upperBound, appView);
-                    propagationState
-                        .inactiveUntilUpperBound
-                        .computeIfAbsent(
-                            bounds, ignoreKey(MethodStateCollectionBySignature::create))
-                        .addMethodState(appView, method, methodStateForBounds);
+                    propagationState.addInactiveUntilUpperBound(
+                        bounds, method, methodStateForBounds);
                   }
                 }
               });
         });
 
+    assert propagationState.verifyActiveUntilLowerBoundRelevance(clazz);
     propagationStates.put(clazz, propagationState);
   }
 
-  @SuppressWarnings("ReferenceEquality")
   private boolean isUpperBoundSatisfied(ClassTypeElement upperBound, DexProgramClass currentClass) {
     DexType upperBoundType =
-        upperBound.getClassType() == appView.dexItemFactory().objectType
+        upperBound.getClassType().isIdenticalTo(appView.dexItemFactory().objectType)
                 && upperBound.getInterfaces().hasSingleKnownInterface()
             ? upperBound.getInterfaces().getSingleKnownInterface()
             : upperBound.getClassType();
@@ -315,7 +416,7 @@
 
     // This is a polymorphic method and we need to compute the method state to account for dynamic
     // dispatch.
-    methodState = propagationState.computeMethodStateForPolymorhicMethod(method);
+    methodState = propagationState.computeMethodStateForPolymorphicMethod(method);
     assert !methodState.isConcrete() || methodState.asConcrete().isMonomorphic();
     methodStates.set(method, methodState);
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
index d0ec5b6..07a8bc7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -168,11 +169,11 @@
     return effectivelyUnusedConstraints;
   }
 
-  public void computeEffectivelyUnusedArguments() {
+  public void computeEffectivelyUnusedArguments(PrunedItems prunedItems) {
     // Build a graph where nodes are method parameters and there is an edge from method parameter p0
     // to method parameter p1 if the removal of p0 depends on the removal of p1.
     EffectivelyUnusedArgumentsGraph dependenceGraph =
-        EffectivelyUnusedArgumentsGraph.create(appView, constraints);
+        EffectivelyUnusedArgumentsGraph.create(appView, constraints, prunedItems);
 
     // Remove all unoptimizable method parameters from the graph, as well as all nodes that depend
     // on a node that is unoptimable.
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
index 9fbd408..964fca7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -37,13 +38,14 @@
 
   public static EffectivelyUnusedArgumentsGraph create(
       AppView<AppInfoWithLiveness> appView,
-      Map<MethodParameter, Set<MethodParameter>> constraints) {
+      Map<MethodParameter, Set<MethodParameter>> constraints,
+      PrunedItems prunedItems) {
     EffectivelyUnusedArgumentsGraph graph = new EffectivelyUnusedArgumentsGraph(appView);
     constraints.forEach(
         (methodParameter, constraintsForMethodParameter) -> {
           EffectivelyUnusedArgumentsGraphNode node = graph.getOrCreateNode(methodParameter);
           for (MethodParameter constraint : constraintsForMethodParameter) {
-            graph.addConstraintEdge(node, constraint, constraints);
+            graph.addConstraintEdge(node, constraint, constraints, prunedItems);
           }
         });
     return graph;
@@ -52,12 +54,19 @@
   void addConstraintEdge(
       EffectivelyUnusedArgumentsGraphNode node,
       MethodParameter constraint,
-      Map<MethodParameter, Set<MethodParameter>> constraints) {
+      Map<MethodParameter, Set<MethodParameter>> constraints,
+      PrunedItems prunedItems) {
+    if (prunedItems.isRemoved(constraint.getMethod())) {
+      // The current parameter node is an argument to a method that has been removed by argument
+      // propagation. Therefore, this usage constraint is no longer blocking the removal of the
+      // parameter.
+      return;
+    }
+
     ProgramMethod dependencyMethod =
         asProgramMethodOrNull(appView.definitionFor(constraint.getMethod()));
     if (dependencyMethod == null) {
       assert false;
-      node.setUnoptimizable();
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
index 9b77365..8b19b02 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
@@ -175,6 +175,12 @@
     return definitions;
   }
 
+  public Set<DexMethod> toReferenceSet(IntFunction<Set<DexMethod>> factory) {
+    Set<DexMethod> definitions = factory.apply(size());
+    forEach(method -> definitions.add(method.getReference()));
+    return definitions;
+  }
+
   public void trimCapacityIfSizeLessThan(int expectedSize) {
     if (size() < expectedSize) {
       Map<DexMethod, T> newBacking = createBacking(size());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/ArgumentPropagationWithInexactUpperBoundTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/ArgumentPropagationWithInexactUpperBoundTest.java
index 71acba4..7cd7781 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/ArgumentPropagationWithInexactUpperBoundTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/ArgumentPropagationWithInexactUpperBoundTest.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
@@ -65,8 +64,7 @@
 
               MethodSubject aMethodSubject = aClassSubject.uniqueMethodWithOriginalName("m");
               assertThat(aMethodSubject, isPresent());
-              // TODO(b/296030319): Should be true.
-              assertFalse(aMethodSubject.streamInstructions().anyMatch(i -> i.isConstNumber(0)));
+              assertTrue(aMethodSubject.streamInstructions().anyMatch(i -> i.isConstNumber(0)));
 
               ClassSubject cClassSubject = inspector.clazz(C.class);
               assertThat(cClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/SiblingInterfaceMethodPropagationWithLowerBoundTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/SiblingInterfaceMethodPropagationWithLowerBoundTest.java
index 2996f59..ef38d6e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/SiblingInterfaceMethodPropagationWithLowerBoundTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/SiblingInterfaceMethodPropagationWithLowerBoundTest.java
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
@@ -58,8 +58,7 @@
 
               MethodSubject bMethodSubject = bClassSubject.uniqueMethodWithOriginalName("m");
               assertThat(bMethodSubject, isPresent());
-              // TODO(b/296030319): Should be true.
-              assertFalse(bMethodSubject.streamInstructions().anyMatch(i -> i.isConstNumber(2)));
+              assertTrue(bMethodSubject.streamInstructions().anyMatch(i -> i.isConstNumber(2)));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccess();