Narrow unknown state using static type in value propagation

Fixes: b/296030319
Bug: b/330674939
Change-Id: I6eef571e1acb1cb28fb6338c50a0a037c6876294
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index aa7f88a..b3ab15a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -85,6 +85,10 @@
     return DynamicType.create(appView, toTypeElement(appView, nullability));
   }
 
+  public TypeElement toNonNullTypeElement(AppView<?> appView) {
+    return toTypeElement(appView, Nullability.definitelyNotNull());
+  }
+
   public TypeElement toTypeElement(AppView<?> appView) {
     return toTypeElement(appView, Nullability.maybeNull());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 8cd6e37..af58cdc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -55,6 +55,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
+// TODO(b/330674939): Remove legacy field optimizations.
 public class FieldAssignmentTracker {
 
   private final AbstractValueFactory abstractValueFactory;
@@ -240,8 +241,9 @@
           assert fieldState.isClassState();
 
           ConcreteClassTypeValueState classFieldState = fieldState.asClassState();
+          DexType inStaticType = null;
           return classFieldState.mutableJoin(
-              appView, abstractValue, value.getDynamicType(appView), field);
+              appView, abstractValue, value.getDynamicType(appView), inStaticType, field);
         });
   }
 
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 e31d2db..971a56e 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
@@ -155,25 +155,44 @@
     return false;
   }
 
-  public DynamicType join(AppView<AppInfoWithLiveness> appView, DynamicType dynamicType) {
-    if (isBottom()) {
-      return dynamicType;
-    }
-    if (dynamicType.isBottom() || equals(dynamicType)) {
+  public final DynamicType join(AppView<AppInfoWithLiveness> appView, DynamicType dynamicType) {
+    return join(appView, dynamicType, null, null);
+  }
+
+  public DynamicType join(
+      AppView<AppInfoWithLiveness> appView,
+      DynamicType inDynamicType,
+      DexType inStaticType,
+      DexType outStaticType) {
+    if (equals(inDynamicType)) {
       return this;
     }
-    if (isUnknown() || dynamicType.isUnknown()) {
+    if (isBottom()) {
+      // Account for the fact that the in-static-type may be more precise than the static type of
+      // the current dynamic type.
+      if (inDynamicType.isNotNullType() && inStaticType != null) {
+        return create(appView, inStaticType.toNonNullTypeElement(appView));
+      }
+      return inDynamicType;
+    }
+    if (inDynamicType.isBottom()) {
+      return this;
+    }
+    if (isUnknown() || inDynamicType.isUnknown()) {
       return unknown();
     }
-    if (isNotNullType() || dynamicType.isNotNullType()) {
-      if (getNullability().isNullable() || dynamicType.getNullability().isNullable()) {
-        return unknown();
+    if (isNotNullType()) {
+      return inDynamicType.getNullability().isNullable() ? unknown() : this;
+    }
+    if (inDynamicType.isNotNullType()) {
+      if (inStaticType == null || inStaticType.isIdenticalTo(outStaticType)) {
+        return getNullability().isNullable() ? unknown() : inDynamicType;
       }
-      return definitelyNotNull();
+      inDynamicType = create(appView, inStaticType.toNonNullTypeElement(appView));
     }
     assert isDynamicTypeWithUpperBound();
-    assert dynamicType.isDynamicTypeWithUpperBound();
-    return asDynamicTypeWithUpperBound().join(appView, dynamicType.asDynamicTypeWithUpperBound());
+    assert inDynamicType.isDynamicTypeWithUpperBound();
+    return asDynamicTypeWithUpperBound().join(appView, inDynamicType.asDynamicTypeWithUpperBound());
   }
 
   public abstract DynamicType rewrittenWithLens(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 62573c9..65487d8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -968,6 +968,13 @@
                 ordinalToObjectState.put(ordinal, enumState);
                 if (isEnumWithSubtypes) {
                   DynamicType dynamicType = field.getOptimizationInfo().getDynamicType();
+                  // If the dynamic type is a NotNull dynamic type, then de-canonicalize the dynamic
+                  // type. If the static type is an effectively final class then this yields an
+                  // exact dynamic type.
+                  if (dynamicType.isNotNullType()) {
+                    dynamicType =
+                        DynamicType.create(appView, field.getType().toNonNullTypeElement(appView));
+                  }
                   if (dynamicType.isExactClassType()) {
                     valueTypes.put(ordinal, dynamicType.getExactClassType().getClassType());
                   } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index bdb5ede..c2b8988 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -756,7 +756,7 @@
 
   private void computeAbstractFunction(IRCode code, OptimizationFeedback feedback) {
     if (ComposeUtils.isUpdateChangedFlags(code, appView.dexItemFactory())) {
-      MethodParameter methodParameter = new MethodParameter(code.context().getReference(), 0);
+      MethodParameter methodParameter = new MethodParameter(code.context(), 0);
       UpdateChangedFlagsAbstractFunction updateChangedFlagsAbstractFunction =
           new UpdateChangedFlagsAbstractFunction(methodParameter);
       feedback.setAbstractFunction(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 93522cc..4e4d506 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -97,10 +97,11 @@
   }
 
   private MutableFieldOptimizationInfo setAbstractValue(AbstractValue abstractValue) {
-    assert getAbstractValue().isUnknown()
-        || abstractValue.isNonTrivial()
-        || (getAbstractValue().isNullOrAbstractValue()
-            && getAbstractValue().asNullOrAbstractValue().getNonNullValue().isSingleFieldValue());
+    // TODO(b/330674939): Reenable assert after removing legacy field optimizations.
+    /*assert getAbstractValue().isUnknown()
+    || abstractValue.isNonTrivial()
+    || (getAbstractValue().isNullOrAbstractValue()
+        && getAbstractValue().asNullOrAbstractValue().getNonNullValue().isSingleFieldValue());*/
     this.abstractValue = abstractValue;
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 7ee9fb5..4f374e6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -159,17 +159,26 @@
 
   // TODO(b/296030319): Allow lookups in the FieldStateCollection using DexField keys to avoid the
   //  need for definitionFor here.
-  private boolean isFieldValueAlreadyUnknown(DexField field) {
-    return isFieldValueAlreadyUnknown(appView.definitionFor(field).asProgramField());
+  private boolean isFieldValueAlreadyUnknown(DexType staticType, DexField field) {
+    return isFieldValueAlreadyUnknown(staticType, appView.definitionFor(field).asProgramField());
   }
 
-  private boolean isFieldValueAlreadyUnknown(ProgramField field) {
-    return fieldStates.get(field).isUnknown();
+  private boolean isFieldValueAlreadyUnknown(DexType staticType, ProgramField field) {
+    // Only allow early graph pruning when the two nodes have the same type. If the given field is
+    // unknown, but flows to a field or method parameter with a less precise type, we still want
+    // this type propagation to happen.
+    return fieldStates.get(field).isUnknown() && field.getType().isIdenticalTo(staticType);
   }
 
   protected boolean isMethodParameterAlreadyUnknown(
-      MethodParameter methodParameter, ProgramMethod method) {
+      DexType staticType, MethodParameter methodParameter, ProgramMethod method) {
     assert methodParameter.getMethod().isIdenticalTo(method.getReference());
+    if (methodParameter.getType().isNotIdenticalTo(staticType)) {
+      // Only allow early graph pruning when the two nodes have the same type. If the given method
+      // parameter is unknown, but flows to a field or method parameter with a less precise type,
+      // we still want this type propagation to happen.
+      return false;
+    }
     MethodState methodState =
         methodStates.get(
             method.getDefinition().belongsToDirectPool() || isMonomorphicVirtualMethod(method)
@@ -240,10 +249,12 @@
         () -> computeFieldState(fieldPut, field, abstractValueSupplier, context, timing),
         timing,
         (existingFieldState, fieldStateToAdd) -> {
+          DexType inStaticType = null;
           NonEmptyValueState newFieldState =
               existingFieldState.mutableJoin(
                   appView,
                   fieldStateToAdd,
+                  inStaticType,
                   field.getType(),
                   StateCloner.getCloner(),
                   Action.empty());
@@ -306,12 +317,12 @@
   // If the value is an argument of the enclosing method or defined by a field-get, then clearly we
   // have no information about its abstract value (yet). Instead of treating this as having an
   // unknown runtime value, we instead record a flow constraint.
-  private InFlow computeInFlow(Value value, ProgramMethod context) {
+  private InFlow computeInFlow(DexType staticType, Value value, ProgramMethod context) {
     Value valueRoot = value.getAliasedValue(aliasedValueConfiguration);
     if (valueRoot.isArgument()) {
       MethodParameter inParameter =
           methodParameterFactory.create(context, valueRoot.getDefinition().asArgument().getIndex());
-      return castBaseInFlow(widenBaseInFlow(inParameter, context), value);
+      return castBaseInFlow(widenBaseInFlow(staticType, inParameter, context), value);
     } else if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isFieldGet)) {
       FieldGet fieldGet = valueRoot.getDefinition().asFieldGet();
       ProgramField field = fieldGet.resolveField(appView, context).getProgramField();
@@ -320,13 +331,15 @@
       }
       if (fieldGet.isInstanceGet()) {
         Value receiverValue = fieldGet.asInstanceGet().object();
-        BaseInFlow receiverInFlow = asBaseInFlowOrNull(computeInFlow(receiverValue, context));
+        BaseInFlow receiverInFlow =
+            asBaseInFlowOrNull(computeInFlow(staticType, receiverValue, context));
         if (receiverInFlow != null
-            && receiverInFlow.equals(widenBaseInFlow(receiverInFlow, context))) {
+            && receiverInFlow.equals(widenBaseInFlow(staticType, receiverInFlow, context))) {
           return new InstanceFieldReadAbstractFunction(receiverInFlow, field.getReference());
         }
       }
-      return castBaseInFlow(widenBaseInFlow(fieldValueFactory.create(field), context), value);
+      return castBaseInFlow(
+          widenBaseInFlow(staticType, fieldValueFactory.create(field), context), value);
     }
     return null;
   }
@@ -344,14 +357,14 @@
     return new CastAbstractFunction(inFlow.asBaseInFlow(), checkCast.getType());
   }
 
-  private InFlow widenBaseInFlow(BaseInFlow inFlow, ProgramMethod context) {
+  private InFlow widenBaseInFlow(DexType staticType, BaseInFlow inFlow, ProgramMethod context) {
     if (inFlow.isFieldValue()) {
-      if (isFieldValueAlreadyUnknown(inFlow.asFieldValue().getField())) {
+      if (isFieldValueAlreadyUnknown(staticType, inFlow.asFieldValue().getField())) {
         return AbstractFunction.unknown();
       }
     } else {
       assert inFlow.isMethodParameter();
-      if (isMethodParameterAlreadyUnknown(inFlow.asMethodParameter(), context)) {
+      if (isMethodParameterAlreadyUnknown(staticType, inFlow.asMethodParameter(), context)) {
         return AbstractFunction.unknown();
       }
     }
@@ -360,7 +373,7 @@
 
   private NonEmptyValueState computeInFlowState(
       DexType staticType, Value value, ProgramMethod context) {
-    InFlow inFlow = computeInFlow(value, context);
+    InFlow inFlow = computeInFlow(staticType, value, context);
     if (inFlow == null) {
       return null;
     }
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 0aca617..be9d7c5 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
@@ -13,6 +13,7 @@
 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.DynamicTypeWithUpperBound;
 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;
@@ -154,6 +155,16 @@
   }
 
   private void setDynamicType(ProgramField field, DynamicType dynamicType) {
+    if (dynamicType.hasDynamicUpperBoundType()) {
+      DynamicTypeWithUpperBound dynamicTypeWithUpperBound =
+          dynamicType.asDynamicTypeWithUpperBound();
+      if (!dynamicTypeWithUpperBound.strictlyLessThan(
+          field.getType().toTypeElement(appView), appView)) {
+        // TODO(b/296030319): Try to guarantee that the computed dynamic type is strictly more
+        //  precise than the static type.
+        return;
+      }
+    }
     getSimpleFeedback().markFieldHasDynamicType(field, dynamicType);
   }
 
@@ -298,14 +309,16 @@
         continue;
       }
       DynamicType dynamicType = parameterState.asClassState().getDynamicType();
-      DexType staticType = method.getArgumentType(argumentIndex);
-      if (shouldWidenDynamicTypeToUnknown(dynamicType, staticType)) {
+      DexType outStaticType = method.getArgumentType(argumentIndex);
+      if (shouldWidenDynamicTypeToUnknown(dynamicType, outStaticType)) {
+        DexType inStaticType = null;
         methodState.setParameterState(
             argumentIndex,
             parameterState.mutableJoin(
                 appView,
                 new ConcreteClassTypeValueState(AbstractValue.bottom(), DynamicType.unknown()),
-                staticType,
+                inStaticType,
+                outStaticType,
                 StateCloner.getIdentity()));
       }
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 5b5b80c..3f9880b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -671,8 +671,10 @@
 
       KeepFieldInfo keepInfo = appView.getKeepInfo(field);
 
-      // We don't have dynamic type information for fields that are kept.
-      assert !keepInfo.isPinned(options);
+      // We don't have dynamic type information for fields that are kept, unless the static type of
+      // the field is guaranteed to be null.
+      assert !keepInfo.isPinned(options)
+          || (field.getType().isAlwaysNull(appView) && dynamicType.isNullType());
 
       if (!keepInfo.isFieldTypeStrengtheningAllowed(options)) {
         return staticType;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java
index 98e6bfc..dafacba 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java
@@ -23,19 +23,20 @@
   @Override
   public ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState state,
-      DexType staticType,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (state.isBottom()) {
+    if (inState.isBottom()) {
       return this;
     }
-    if (state.isUnknown()) {
-      return state;
+    if (inState.isUnknown()) {
+      return inState;
     }
-    assert state.isConcrete();
-    assert state.asConcrete().isReferenceState();
-    ConcreteReferenceTypeValueState concreteState = state.asConcrete().asReferenceState();
+    assert inState.isConcrete();
+    assert inState.asConcrete().isReferenceState();
+    ConcreteReferenceTypeValueState concreteState = inState.asConcrete().asReferenceState();
     if (concreteState.isArrayState()) {
       return cloner.mutableCopy(concreteState);
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java
index f8745d8..b44361c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java
@@ -25,29 +25,43 @@
   @Override
   public ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState state,
-      DexType staticType,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (state.isBottom()) {
+    if (inState.isBottom()) {
       return this;
     }
-    if (state.isUnknown()) {
-      return state;
+    if (inState.isUnknown()) {
+      return inState;
     }
-    assert state.isConcrete();
-    assert state.asConcrete().isReferenceState();
-    ConcreteReferenceTypeValueState concreteState = state.asConcrete().asReferenceState();
-    AbstractValue abstractValue = concreteState.getAbstractValue(appView);
-    DynamicType dynamicType = concreteState.getDynamicType();
-    DynamicType widenedDynamicType =
-        WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, staticType);
-    if (concreteState.isClassState() && !widenedDynamicType.isUnknown()) {
+    assert inState.isConcrete();
+    assert inState.asConcrete().isReferenceState();
+    ConcreteReferenceTypeValueState concreteState = inState.asConcrete().asReferenceState();
+    DynamicType joinedDynamicType =
+        joinDynamicType(appView, concreteState.getDynamicType(), inStaticType, outStaticType);
+    if (concreteState.isClassState() && concreteState.getDynamicType().equals(joinedDynamicType)) {
       return cloner.mutableCopy(concreteState);
     }
-    return abstractValue.isUnknown() && widenedDynamicType.isUnknown()
-        ? unknown()
-        : new ConcreteClassTypeValueState(
-            abstractValue, widenedDynamicType, concreteState.copyInFlow());
+    AbstractValue abstractValue = concreteState.getAbstractValue(appView);
+    return ConcreteClassTypeValueState.create(
+        abstractValue, joinedDynamicType, concreteState.copyInFlow());
+  }
+
+  private DynamicType joinDynamicType(
+      AppView<AppInfoWithLiveness> appView,
+      DynamicType inDynamicType,
+      DexType inStaticType,
+      DexType outStaticType) {
+    DynamicType oldDynamicType = DynamicType.bottom();
+    DynamicType joinedDynamicType =
+        oldDynamicType.join(appView, inDynamicType, inStaticType, outStaticType);
+    if (outStaticType != null) {
+      return WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, outStaticType);
+    } else {
+      assert !joinedDynamicType.isUnknown();
+      return joinedDynamicType;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
index 567a559..67c0736 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
@@ -22,18 +22,19 @@
   @Override
   public ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState state,
-      DexType staticType,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (state.isBottom()) {
-      assert state == bottomPrimitiveTypeState();
+    if (inState.isBottom()) {
+      assert inState == bottomPrimitiveTypeState();
       return this;
     }
-    if (state.isUnknown()) {
-      return state;
+    if (inState.isUnknown()) {
+      return inState;
     }
-    assert state.isPrimitiveState();
-    return cloner.mutableCopy(state);
+    assert inState.isPrimitiveState();
+    return cloner.mutableCopy(inState);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverValueState.java
index b8eb62e..9a9bb39 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverValueState.java
@@ -23,19 +23,20 @@
   @Override
   public ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState state,
-      DexType staticType,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (state.isBottom()) {
+    if (inState.isBottom()) {
       return this;
     }
-    if (state.isUnknown()) {
-      return state;
+    if (inState.isUnknown()) {
+      return inState;
     }
-    assert state.isConcrete();
-    assert state.asConcrete().isReferenceState();
-    ConcreteReferenceTypeValueState concreteState = state.asConcrete().asReferenceState();
+    assert inState.isConcrete();
+    assert inState.asConcrete().isReferenceState();
+    ConcreteReferenceTypeValueState concreteState = inState.asConcrete().asReferenceState();
     if (concreteState.isReceiverState()) {
       return cloner.mutableCopy(concreteState);
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
index 722aef5..6e17619 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
@@ -109,15 +109,16 @@
   @Override
   public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeValueState state,
-      DexType staticType,
+      ConcreteReferenceTypeValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       Action onChangedAction) {
-    assert staticType.isArrayType();
-    boolean nullabilityChanged = mutableJoinNullability(state.getNullability());
+    assert outStaticType.isArrayType();
+    boolean nullabilityChanged = mutableJoinNullability(inState.getNullability());
     if (isEffectivelyUnknown()) {
       return unknown();
     }
-    boolean inFlowChanged = mutableJoinInFlow(state);
+    boolean inFlowChanged = mutableJoinInFlow(inState);
     if (widenInFlow(appView)) {
       return unknown();
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
index 320c89c..8542763 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
@@ -40,9 +40,14 @@
   }
 
   public static NonEmptyValueState create(AbstractValue abstractValue, DynamicType dynamicType) {
+    return create(abstractValue, dynamicType, Collections.emptySet());
+  }
+
+  public static NonEmptyValueState create(
+      AbstractValue abstractValue, DynamicType dynamicType, Set<InFlow> inFlow) {
     return abstractValue.isUnknown() && dynamicType.isUnknown()
         ? ValueState.unknown()
-        : new ConcreteClassTypeValueState(abstractValue, dynamicType);
+        : new ConcreteClassTypeValueState(abstractValue, dynamicType, inFlow);
   }
 
   @Override
@@ -61,7 +66,9 @@
   @Override
   public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
     if (getNullability().isDefinitelyNull()) {
-      assert abstractValue.isNull() || abstractValue.isUnknown();
+      assert abstractValue.isNull()
+          || abstractValue.isNullOrAbstractValue()
+          || abstractValue.isUnknown();
       return appView.abstractValueFactory().createUncheckedNullValue();
     }
     return abstractValue;
@@ -115,11 +122,12 @@
   public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
       AbstractValue abstractValue,
-      DynamicType dynamicType,
+      DynamicType inDynamicType,
+      DexType inStaticType,
       ProgramField field) {
     assert field.getType().isClassType();
     mutableJoinAbstractValue(appView, abstractValue, field.getType());
-    mutableJoinDynamicType(appView, dynamicType, field.getType());
+    mutableJoinDynamicType(appView, inDynamicType, inStaticType, field.getType());
     if (isEffectivelyUnknown()) {
       return unknown();
     }
@@ -129,18 +137,19 @@
   @Override
   public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeValueState state,
-      DexType staticType,
+      ConcreteReferenceTypeValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       Action onChangedAction) {
-    assert staticType.isClassType();
+    assert outStaticType.isClassType();
     boolean abstractValueChanged =
-        mutableJoinAbstractValue(appView, state.getAbstractValue(appView), staticType);
+        mutableJoinAbstractValue(appView, inState.getAbstractValue(appView), outStaticType);
     boolean dynamicTypeChanged =
-        mutableJoinDynamicType(appView, state.getDynamicType(), staticType);
+        mutableJoinDynamicType(appView, inState.getDynamicType(), inStaticType, outStaticType);
     if (isEffectivelyUnknown()) {
       return unknown();
     }
-    boolean inFlowChanged = mutableJoinInFlow(state);
+    boolean inFlowChanged = mutableJoinInFlow(inState);
     if (widenInFlow(appView)) {
       return unknown();
     }
@@ -161,11 +170,15 @@
   }
 
   private boolean mutableJoinDynamicType(
-      AppView<AppInfoWithLiveness> appView, DynamicType otherDynamicType, DexType staticType) {
+      AppView<AppInfoWithLiveness> appView,
+      DynamicType inDynamicType,
+      DexType inStaticType,
+      DexType outStaticType) {
     DynamicType oldDynamicType = dynamicType;
-    DynamicType joinedDynamicType = dynamicType.join(appView, otherDynamicType);
+    DynamicType joinedDynamicType =
+        dynamicType.join(appView, inDynamicType, inStaticType, outStaticType);
     DynamicType widenedDynamicType =
-        WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, staticType);
+        WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, outStaticType);
     dynamicType = widenedDynamicType;
     return !dynamicType.equals(oldDynamicType);
   }
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 b2b1862..e150610 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
@@ -88,19 +88,24 @@
       assert size() == methodSignature.getArity() + 1;
       ValueState parameterState = parameterStates.get(0);
       ValueState otherParameterState = methodState.parameterStates.get(0);
-      DexType parameterType = null;
+      DexType inStaticType = null;
+      DexType outStaticType = null;
       parameterStates.set(
-          0, parameterState.mutableJoin(appView, otherParameterState, parameterType, cloner));
+          0,
+          parameterState.mutableJoin(
+              appView, otherParameterState, inStaticType, outStaticType, cloner));
       argumentIndex++;
     }
 
     for (int parameterIndex = 0; argumentIndex < size(); argumentIndex++, parameterIndex++) {
       ValueState parameterState = parameterStates.get(argumentIndex);
       ValueState otherParameterState = methodState.parameterStates.get(argumentIndex);
-      DexType parameterType = methodSignature.getParameter(parameterIndex);
+      DexType inStaticType = null;
+      DexType outStaticType = methodSignature.getParameter(parameterIndex);
       parameterStates.set(
           argumentIndex,
-          parameterState.mutableJoin(appView, otherParameterState, parameterType, cloner));
+          parameterState.mutableJoin(
+              appView, otherParameterState, inStaticType, outStaticType, cloner));
       assert !parameterStates.get(argumentIndex).isConcrete()
           || !parameterStates.get(argumentIndex).asConcrete().isReceiverState();
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
index 8a1a137..894ad96 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
@@ -99,18 +99,19 @@
   @Override
   public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeValueState state,
-      DexType staticType,
+      ConcreteReferenceTypeValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       Action onChangedAction) {
     // TODO(b/190154391): Always take in the static type as an argument, and unset the dynamic type
     //  if it equals the static type.
-    assert staticType == null || staticType.isClassType();
+    assert outStaticType == null || outStaticType.isClassType();
     boolean dynamicTypeChanged =
-        mutableJoinDynamicType(appView, state.getDynamicType(), staticType);
+        mutableJoinDynamicType(appView, inState.getDynamicType(), inStaticType, outStaticType);
     if (isEffectivelyUnknown()) {
       return unknown();
     }
-    boolean inFlowChanged = mutableJoinInFlow(state);
+    boolean inFlowChanged = mutableJoinInFlow(inState);
     if (widenInFlow(appView)) {
       return unknown();
     }
@@ -121,12 +122,16 @@
   }
 
   private boolean mutableJoinDynamicType(
-      AppView<AppInfoWithLiveness> appView, DynamicType otherDynamicType, DexType staticType) {
+      AppView<AppInfoWithLiveness> appView,
+      DynamicType inDynamicType,
+      DexType inStaticType,
+      DexType outStaticType) {
     DynamicType oldDynamicType = dynamicType;
-    DynamicType joinedDynamicType = dynamicType.join(appView, otherDynamicType);
-    if (staticType != null) {
+    DynamicType joinedDynamicType =
+        dynamicType.join(appView, inDynamicType, inStaticType, outStaticType);
+    if (outStaticType != null) {
       DynamicType widenedDynamicType =
-          WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, staticType);
+          WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, outStaticType);
       assert !widenedDynamicType.isUnknown();
       dynamicType = widenedDynamicType;
     } else {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeValueState.java
index 951ea07..ff432b9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeValueState.java
@@ -41,7 +41,9 @@
       if (typeElement.lessThanOrEqual(lowerBound, appView)) {
         return DynamicType.create(appView, typeElement, lowerBound);
       } else {
-        return DynamicType.bottom();
+        return dynamicType.getNullability().isMaybeNull()
+            ? DynamicType.definitelyNull()
+            : DynamicType.bottom();
       }
     }
     return DynamicType.create(appView, typeElement);
@@ -63,7 +65,8 @@
 
   public abstract NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeValueState state,
-      DexType staticType,
+      ConcreteReferenceTypeValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       Action onChangedAction);
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
index e32324a..d6193f6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
@@ -104,24 +104,30 @@
   @Override
   public final NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState state,
-      DexType staticType,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (state.isBottom()) {
+    if (inState.isBottom()) {
       return this;
     }
-    if (state.isUnknown()) {
+    if (inState.isUnknown()) {
       return unknown();
     }
-    ConcreteValueState concreteState = state.asConcrete();
+    ConcreteValueState concreteState = inState.asConcrete();
     if (isReferenceState()) {
       assert concreteState.isReferenceState();
       return asReferenceState()
-          .mutableJoin(appView, concreteState.asReferenceState(), staticType, onChangedAction);
+          .mutableJoin(
+              appView,
+              concreteState.asReferenceState(),
+              inStaticType,
+              outStaticType,
+              onChangedAction);
     }
     return asPrimitiveState()
-        .mutableJoin(appView, concreteState.asPrimitiveState(), staticType, onChangedAction);
+        .mutableJoin(appView, concreteState.asPrimitiveState(), outStaticType, onChangedAction);
   }
 
   boolean mutableJoinInFlow(ConcreteValueState state) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
index 544c365..cf0dcc5 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
@@ -34,13 +35,16 @@
         field,
         fieldStateSupplier,
         timing,
-        (existingFieldState, fieldStateToAdd) ->
-            existingFieldState.mutableJoin(
-                appView,
-                fieldStateToAdd,
-                field.getType(),
-                StateCloner.getCloner(),
-                Action.empty()));
+        (existingFieldState, fieldStateToAdd) -> {
+          DexType inStaticType = null;
+          return existingFieldState.mutableJoin(
+              appView,
+              fieldStateToAdd,
+              inStaticType,
+              field.getType(),
+              StateCloner.getCloner(),
+              Action.empty());
+        });
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
index 2fe1a2a..7ebb2d0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
@@ -4,17 +4,29 @@
 
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import java.util.Objects;
 
 public class MethodParameter implements BaseInFlow {
 
   private final DexMethod method;
   private final int index;
+  private final boolean isMethodStatic;
 
-  public MethodParameter(DexMethod method, int index) {
+  public MethodParameter(DexClassAndMethod method, int index) {
+    this(method.getReference(), index, method.getAccessFlags().isStatic());
+  }
+
+  public MethodParameter(DexMethod method, int index, boolean isMethodStatic) {
     this.method = method;
     this.index = index;
+    this.isMethodStatic = isMethodStatic;
+  }
+
+  public static MethodParameter createStatic(DexMethod method, int index) {
+    return new MethodParameter(method, index, true);
   }
 
   public DexMethod getMethod() {
@@ -25,6 +37,10 @@
     return index;
   }
 
+  public DexType getType() {
+    return method.getArgumentType(index, isMethodStatic);
+  }
+
   @Override
   public boolean isMethodParameter() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameterFactory.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameterFactory.java
index d1501e6..16267c2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameterFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameterFactory.java
@@ -15,6 +15,6 @@
 
   public MethodParameter create(ProgramMethod method, int argumentIndex) {
     return methodParameters.computeIfAbsent(
-        new MethodParameter(method.getReference(), argumentIndex), Function.identity());
+        new MethodParameter(method, argumentIndex), Function.identity());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
index 6e3601b..843e64d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
@@ -39,8 +39,9 @@
   @Override
   public UnknownValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState state,
-      DexType staticType,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       StateCloner cloner,
       Action onChangedAction) {
     return this;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
index 96adb2c..06cb1eb 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
@@ -120,16 +120,18 @@
 
   public final ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState state,
-      DexType parameterType,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       StateCloner cloner) {
-    return mutableJoin(appView, state, parameterType, cloner, Action.empty());
+    return mutableJoin(appView, inState, inStaticType, outStaticType, cloner, Action.empty());
   }
 
   public abstract ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState state,
-      DexType staticType,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
       StateCloner cloner,
       Action onChangedAction);
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
index 294eb8e..9274830 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+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.ir.analysis.type.DynamicType;
@@ -229,9 +230,11 @@
                   node -> {
                     ProgramField field = node.getField();
                     if (fieldsWithLiveDefaultValue.remove(field)) {
+                      DexType inStaticType = null;
                       node.addState(
                           appView,
                           getDefaultValueState(field),
+                          inStaticType,
                           () -> {
                             if (node.isUnknown()) {
                               node.clearPredecessors();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
index 00d36e6..061c898 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
@@ -203,7 +203,7 @@
     }
 
     ValueState fieldState = getFieldState(field, fieldStates);
-    if (fieldState.isUnknown()) {
+    if (fieldState.isUnknown() && field.getType().isIdenticalTo(node.getStaticType())) {
       // The current node depends on a field for which we don't know anything.
       node.clearPredecessors();
       node.setStateToUnknown();
@@ -237,19 +237,17 @@
       return TraversalContinuation.doContinue();
     }
 
-    if (enclosingMethodState.isUnknown()) {
+    assert enclosingMethodState.isMonomorphic() || enclosingMethodState.isUnknown();
+
+    if (enclosingMethodState.isUnknown() && inFlow.getType().isIdenticalTo(node.getStaticType())) {
       // The current node depends on a parameter for which we don't know anything.
       node.clearPredecessors();
       node.setStateToUnknown();
       return TraversalContinuation.doBreak();
     }
 
-    assert enclosingMethodState.isConcrete();
-    assert enclosingMethodState.asConcrete().isMonomorphic();
-
     FlowGraphParameterNode predecessor =
-        getOrCreateParameterNode(
-            enclosingMethod, inFlow.getIndex(), enclosingMethodState.asConcrete().asMonomorphic());
+        getOrCreateParameterNode(enclosingMethod, inFlow.getIndex(), enclosingMethodState);
     node.addPredecessor(predecessor, transferFunction);
     return TraversalContinuation.doContinue();
   }
@@ -260,7 +258,7 @@
   }
 
   private FlowGraphParameterNode getOrCreateParameterNode(
-      ProgramMethod method, int parameterIndex, ConcreteMonomorphicMethodState methodState) {
+      ProgramMethod method, int parameterIndex, MethodState methodState) {
     Int2ReferenceMap<FlowGraphParameterNode> parameterNodesForMethod =
         parameterNodes.computeIfAbsent(
             method.getReference(), ignoreKey(Int2ReferenceOpenHashMap::new));
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java
index 3547464..5649bbc 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java
@@ -30,11 +30,19 @@
   private boolean inWorklist = true;
 
   void addState(
-      AppView<AppInfoWithLiveness> appView, ConcreteValueState stateToAdd, Action onChangedAction) {
+      AppView<AppInfoWithLiveness> appView,
+      ConcreteValueState inState,
+      DexType inStaticType,
+      Action onChangedAction) {
     ValueState oldState = getState();
     ValueState newState =
         oldState.mutableJoin(
-            appView, stateToAdd, getStaticType(), StateCloner.getCloner(), onChangedAction);
+            appView,
+            inState,
+            inStaticType,
+            getStaticType(),
+            StateCloner.getCloner(),
+            onChangedAction);
     if (newState != oldState) {
       setState(newState);
       onChangedAction.execute();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphParameterNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphParameterNode.java
index eccd2db..12d516b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphParameterNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphParameterNode.java
@@ -5,21 +5,19 @@
 
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
 
 class FlowGraphParameterNode extends FlowGraphNode {
 
   private final ProgramMethod method;
-  private final ConcreteMonomorphicMethodState methodState;
+  private final MethodState methodState;
   private final int parameterIndex;
   private final DexType parameterType;
 
   FlowGraphParameterNode(
-      ProgramMethod method,
-      ConcreteMonomorphicMethodState methodState,
-      int parameterIndex,
-      DexType parameterType) {
+      ProgramMethod method, MethodState methodState, int parameterIndex, DexType parameterType) {
+    assert methodState.isMonomorphic() || methodState.isUnknown();
     this.method = method;
     this.methodState = methodState;
     this.parameterIndex = parameterIndex;
@@ -41,12 +39,18 @@
 
   @Override
   ValueState getState() {
-    return methodState.getParameterState(parameterIndex);
+    return methodState.isMonomorphic()
+        ? methodState.asMonomorphic().getParameterState(parameterIndex)
+        : ValueState.unknown();
   }
 
   @Override
   void setState(ValueState parameterState) {
-    methodState.setParameterState(parameterIndex, parameterState);
+    if (methodState.isMonomorphic()) {
+      methodState.asMonomorphic().setParameterState(parameterIndex, parameterState);
+    } else {
+      assert parameterState.isUnknown();
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index 2aa21de..0794838 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+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.ir.analysis.type.DynamicType;
@@ -235,9 +236,10 @@
         successorNode.setStateToUnknown();
         successorNode.addToWorkList(worklist);
       } else {
-        ConcreteValueState concreteTransferState = transferState.asConcrete();
+        ConcreteValueState inState = transferState.asConcrete();
+        DexType inStaticType = transferFunction.isIdentity() ? node.getStaticType() : null;
         successorNode.addState(
-            appView, concreteTransferState, () -> successorNode.addToWorkList(worklist));
+            appView, inState, inStaticType, () -> successorNode.addToWorkList(worklist));
       }
       // If this successor has become unknown, there is no point in continuing to propagate
       // flow to it from any of its predecessors. We therefore clear the predecessors to
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 07a8bc7..1eec104 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
@@ -120,8 +120,7 @@
       Set<MethodParameter> effectivelyUnusedConstraints =
           computeEffectivelyUnusedConstraints(method, argument, argumentValue);
       if (effectivelyUnusedConstraints != null && !effectivelyUnusedConstraints.isEmpty()) {
-        MethodParameter methodParameter =
-            new MethodParameter(method.getReference(), argument.getIndex());
+        MethodParameter methodParameter = new MethodParameter(method, argument.getIndex());
         assert !constraints.containsKey(methodParameter);
         constraints.put(methodParameter, effectivelyUnusedConstraints);
       }
@@ -161,7 +160,7 @@
           return null;
         }
         effectivelyUnusedConstraints.add(
-            new MethodParameter(resolvedMethod.getReference(), dependentArgumentIndex));
+            new MethodParameter(resolvedMethod, dependentArgumentIndex));
       } else {
         return null;
       }
@@ -222,7 +221,7 @@
     for (int argumentIndex = 0;
         argumentIndex < method.getDefinition().getNumberOfArguments();
         argumentIndex++) {
-      constraints.remove(new MethodParameter(method.getReference(), argumentIndex));
+      constraints.remove(new MethodParameter(method, argumentIndex));
     }
   }
 }
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 964fca7..1f795ad 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
@@ -116,7 +116,7 @@
     assert node.getSuccessors().isEmpty();
     assert node.getPredecessors().isEmpty();
     MethodParameter methodParameter =
-        new MethodParameter(node.getMethod().getReference(), node.getArgumentIndex());
+        new MethodParameter(node.getMethod(), node.getArgumentIndex());
     EffectivelyUnusedArgumentsGraphNode removed = nodes.remove(methodParameter);
     assert removed == node;
   }
@@ -168,7 +168,7 @@
 
   boolean verifyContains(EffectivelyUnusedArgumentsGraphNode node) {
     MethodParameter methodParameter =
-        new MethodParameter(node.getMethod().getReference(), node.getArgumentIndex());
+        new MethodParameter(node.getMethod(), node.getArgumentIndex());
     return nodes.containsKey(methodParameter);
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
index 5d0b693..174e8ba 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.optimize.compose;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.AbstractValueSupplier;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -38,7 +39,7 @@
 
   @Override
   protected boolean isMethodParameterAlreadyUnknown(
-      MethodParameter methodParameter, ProgramMethod method) {
+      DexType staticType, MethodParameter methodParameter, ProgramMethod method) {
     // We haven't defined the virtual root mapping, so we can't tell.
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
index 846dcbe..940a341 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
@@ -186,7 +186,9 @@
         .addTemporaryFieldState(
             appView,
             field,
-            () -> new ConcretePrimitiveTypeValueState(new MethodParameter(method, parameterIndex)),
+            () ->
+                new ConcretePrimitiveTypeValueState(
+                    MethodParameter.createStatic(method, parameterIndex)),
             Timing.empty());
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 0c816c4..f4c915f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -128,7 +128,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaring"))
         .run(Paths.get(ToolHelper.THIRD_PARTY_DIR, "examplesAndroidOLegacy"));
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -167,7 +167,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaring"))
         .run(Paths.get(ToolHelper.THIRD_PARTY_DIR, "examplesAndroidOLegacy"));
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -205,7 +205,7 @@
         .withDexCheck(
             inspector ->
                 checkLambdaCount(
-                    inspector, enableProguardCompatibilityMode ? 4 : 5, "lambdadesugaringnplus"))
+                    inspector, enableProguardCompatibilityMode ? 3 : 4, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -217,10 +217,7 @@
             b ->
                 b.addProguardConfiguration(
                     getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
-        .withDexCheck(
-            inspector ->
-                checkLambdaCount(
-                    inspector, enableProguardCompatibilityMode ? 1 : 2, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 0, "lambdadesugaringnplus"))
         .run();
   }
 
@@ -250,7 +247,7 @@
             b ->
                 b.addProguardConfiguration(
                     getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
index b1c5187..51c434b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -46,6 +47,7 @@
             inspector ->
                 inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
         .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters)
@@ -72,7 +74,7 @@
   @NeverClassInline
   static class A {
 
-    private final C c;
+    @NeverPropagateValue private final C c;
 
     A(Object unused, C c) {
       this.c = c;
@@ -87,7 +89,7 @@
   @NeverClassInline
   static class B {
 
-    private final D d;
+    @NeverPropagateValue private final D d;
 
     B(Object unused, D d) {
       this.d = d;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 780e944..12e89e5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -321,7 +321,7 @@
     // These constants describe the expected number of invoke instructions calling a possibly
     // inlined method.
     int ALWAYS_INLINABLE = 0;
-    int INLINABLE_WHEN_DISABLED = allowAccessModification ? 1 : 0;
+    int INLINABLE_WHEN_DISABLED = allowAccessModification && parameters.isDexRuntime() ? 1 : 0;
     int NEVER_INLINABLE = 1;
 
     ClassSubject clazz = inspector.clazz(nullabilityClass);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineLibraryInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineLibraryInterfaceMethodTest.java
index 7e61ae9..9ec6561 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineLibraryInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineLibraryInterfaceMethodTest.java
@@ -57,8 +57,8 @@
             });
 
     // TODO(b/126323172) Add test here to check the same with desugared lambdas.
-    assertEquals(0, counts.run);
-    assertEquals(2, counts.println);
+    assertEquals(1, counts.run);
+    assertEquals(1, counts.println);
   }
 
   private static long countInvokesWithName(MethodSubject methodSubject, String name) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index eb3ffad..198ffe2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -14,6 +14,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -205,6 +206,16 @@
                                         "kotlin.sequences.TransformingSequence"),
                                     Reference.classFromTypeName(
                                         "kotlin.sequences.GeneratorSequence"))
+                                .applyIf(
+                                    kotlinParameters
+                                        .getCompiler()
+                                        .getCompilerVersion()
+                                        .isEqualTo(KotlinCompilerVersion.KOTLINC_1_9_21),
+                                    i ->
+                                        i.assertIsCompleteMergeGroup(
+                                            SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 0),
+                                            SyntheticItemsTestUtils.syntheticLambdaClass(
+                                                mainKt, 1)))
                                 .assertNoOtherClassesMerged();
                           } else {
                             assert kotlinParameters.getLambdaGeneration().isInvokeDynamic()
@@ -228,7 +239,7 @@
                 assertThat(
                     inspector.clazz(
                         "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"),
-                    isPresent());
+                    isAbsent());
                 assertThat(
                     inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"),
                     isPresent());
@@ -258,7 +269,7 @@
                     isAbsent());
                 assertThat(
                     inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"),
-                    isAbsentIf(testParameters.isDexRuntime()));
+                    isPresent());
               }
             });
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java
index 75c0867..fb66676 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java
@@ -13,8 +13,6 @@
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -76,7 +74,8 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(getProgramFiles())
         .addKeepMainRule(getMainClassName())
-        .addHorizontallyMergedClassesInspector(inspector -> inspect(inspector, lambdasInInput))
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .allowAccessModification(allowAccessModification)
         .allowDiagnosticWarningMessages()
         .setMinApi(parameters)
@@ -88,43 +87,6 @@
         .assertSuccessWithOutput(getExpectedOutput());
   }
 
-  private void inspect(
-      HorizontallyMergedClassesInspector inspector, KotlinLambdasInInput lambdasInInput) {
-    if (parameters.isCfRuntime()) {
-      inspector.assertNoClassesMerged();
-      return;
-    }
-
-    if (kotlinParameters.getLambdaGeneration().isClass()) {
-      inspector
-          .assertIsCompleteMergeGroup(
-              lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$1"),
-              lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$2"),
-              lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$3"),
-              lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$4"))
-          .assertIsCompleteMergeGroup(
-              lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$5"),
-              lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$6"),
-              lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$7"),
-              lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$8"))
-          .assertNoOtherClassesMerged();
-    } else {
-      ClassReference mainKt = Reference.classFromTypeName(getMainClassName());
-      inspector
-          .assertIsCompleteMergeGroup(
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 0),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 2),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 3),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 4))
-          .assertIsCompleteMergeGroup(
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 5),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 6),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 7),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 8))
-          .assertNoOtherClassesMerged();
-    }
-  }
-
   private void inspect(CodeInspector inspector, KotlinLambdasInInput lambdasInInput) {
     List<ClassReference> presentKStyleLambdas = new ArrayList<>();
     for (ClassReference classReference : lambdasInInput.getKStyleLambdas()) {
@@ -132,11 +94,7 @@
         presentKStyleLambdas.add(classReference);
       }
     }
-    assertEquals(
-        parameters.isCfRuntime() || kotlinParameters.getLambdaGeneration().isInvokeDynamic()
-            ? 0
-            : 5,
-        presentKStyleLambdas.size());
+    assertEquals(0, presentKStyleLambdas.size());
   }
 
   private String getExpectedOutput() {
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
index c6c7823..2fa8198 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -108,6 +109,12 @@
         inspector
             .assertIsCompleteMergeGroup(
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testFirst$1"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testFirst$2"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testFirst$3"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testFirst$4"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testFirst$5"),
@@ -120,6 +127,12 @@
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testFirst$9"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testSecond$1"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testSecond$2"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testSecond$3"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testSecond$4"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testSecond$5"),
@@ -130,20 +143,7 @@
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testSecond$8"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testSecond$9"))
-            .assertIsCompleteMergeGroup(
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testFirst$1"),
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testFirst$2"),
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testFirst$3"),
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testSecond$1"),
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testSecond$2"),
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testSecond$3"),
+                    getTestName(), "MainKt$testSecond$9"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testThird$1"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
@@ -167,17 +167,6 @@
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testFirst$3"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testSecond$1"),
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testSecond$2"),
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testSecond$3"),
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testThird$1"),
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testThird$2"))
-            .assertIsCompleteMergeGroup(
-                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testFirst$4"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testFirst$5"),
@@ -190,6 +179,12 @@
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testFirst$9"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testSecond$1"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testSecond$2"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testSecond$3"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testSecond$4"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testSecond$5"),
@@ -200,12 +195,16 @@
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testSecond$8"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testSecond$9"))
+                    getTestName(), "MainKt$testSecond$9"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testThird$1"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testThird$2"))
             .assertNoOtherClassesMerged();
       } else {
         ClassReference mainKt = Reference.classFromTypeName(getMainClassName());
-        inspector
-            .assertIsCompleteMergeGroup(
+        List<ClassReference> mergeGroup =
+            ImmutableList.of(
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 9),
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 10),
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 11),
@@ -217,8 +216,9 @@
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 20),
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 21),
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 22),
-                SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 23))
-            .assertIsCompleteMergeGroup(
+                SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 23));
+        List<ClassReference> otherMergeGroup =
+            ImmutableList.of(
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 0),
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 1),
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 2),
@@ -230,7 +230,21 @@
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 8),
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 15),
                 SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 16),
-                SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 17))
+                SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 17));
+        inspector
+            .applyIf(
+                kotlinc.is(KotlinCompilerVersion.KOTLINC_1_9_21)
+                    && parameters.isDexRuntime()
+                    && lambdaGeneration.isInvokeDynamic(),
+                i ->
+                    i.assertIsCompleteMergeGroup(
+                        ImmutableList.<ClassReference>builder()
+                            .addAll(mergeGroup)
+                            .addAll(otherMergeGroup)
+                            .build()),
+                i ->
+                    i.assertIsCompleteMergeGroup(mergeGroup)
+                        .assertIsCompleteMergeGroup(otherMergeGroup))
             .assertNoOtherClassesMerged();
       }
     }
@@ -244,7 +258,7 @@
       }
     }
     assertEquals(
-        kotlinParameters.getLambdaGeneration().isInvokeDynamic() ? 0 : 2, lambdasInOutput.size());
+        kotlinParameters.getLambdaGeneration().isInvokeDynamic() ? 0 : 1, lambdasInOutput.size());
   }
 
   private String getExpectedOutput() {
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
index e60c8b6..3665e88 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -152,20 +152,17 @@
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 0),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 1))
           .assertIsCompleteMergeGroup(
+              SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 0),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 2),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 3))
           .assertIsCompleteMergeGroup(
               SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 1),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 6),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 7))
-          .assertIsCompleteMergeGroup(
-              SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 0),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 4),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 5))
           .assertClassReferencesNotMerged(
               SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 2),
               SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 3));
-      for (int id = 8; id < 30; id++) {
+      for (int id = 6; id < 30; id++) {
         inspector.assertClassReferencesNotMerged(
             SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, id));
       }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java
index 2b9fc2d..059bec4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java
@@ -9,7 +9,6 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -107,22 +106,27 @@
       inspector.applyIf(
           kotlinParameters.getLambdaGeneration().isClass(),
           i -> {
-            List<ClassReference> group = new ArrayList<>();
-            group.add(
+            i.assertIsCompleteMergeGroup(
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                    getTestName(), "MainKt$testStateless$11"));
-            group.add(
+                    getTestName(), "MainKt$testStateless$3"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testStateless$4"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testStateless$5"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testStateless$6"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testStateless$7"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testStateless$8"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testStateless$9"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testStateless$10"),
+                lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                    getTestName(), "MainKt$testStateless$11"),
                 lambdasInInput.getKStyleLambdaReferenceFromTypeName(
                     getTestName(), "MainKt$testStateless$12"));
-            if (kotlinParameters
-                .getCompilerVersion()
-                .isBetweenBothIncluded(
-                    KotlinCompilerVersion.KOTLINC_1_5_0, KotlinCompilerVersion.KOTLINC_1_6_0)) {
-              group.add(
-                  lambdasInInput.getKStyleLambdaReferenceFromTypeName(
-                      getTestName(), "MainKt$testStateless$6"));
-            }
-            i.assertIsCompleteMergeGroup(group);
           },
           i ->
               i.assertIsCompleteMergeGroup(
@@ -140,7 +144,10 @@
       }
     }
     assertEquals(
-        kotlinParameters.getLambdaGeneration().isInvokeDynamic() ? 0 : 1, lambdasInOutput.size());
+        kotlinParameters.getLambdaGeneration().isInvokeDynamic()
+            ? 0
+            : allowAccessModification && parameters.isCfRuntime() ? 1 : 2,
+        lambdasInOutput.size());
   }
 
   private String getExpectedOutput() {
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 8c3f9dc..9abbb74 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -102,6 +102,8 @@
         "}",
         "-neverclassinline class *",
         "-neverinline @adaptresourcefilenames.NoInliningOfDefaultInitializer class * { <init>(); }",
+        "-nohorizontalclassmerging class adaptresourcefilenames.B",
+        "-nohorizontalclassmerging class adaptresourcefilenames.B$Inner",
         "-nohorizontalclassmerging class adaptresourcefilenames.pkg.C",
         "-nohorizontalclassmerging class adaptresourcefilenames.pkg.innerpkg.D");
   }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
index e7ef37a..5bc8990 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming.applymapping.sourcelibrary;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
@@ -147,7 +147,7 @@
     ClassSubject sub = inspector.clazz(ConcreteChecker.class);
     assertThat(sub, isPresent());
     FieldSubject q = sub.field("java.lang.String", "tag");
-    assertThat(q, isAbsent());
+    assertThat(q, isPresentIf(parameters.canInitNewInstanceUsingSuperclassConstructor()));
     MethodSubject y = sub.method("void", "check", ImmutableList.of());
     assertThat(y, isPresentAndRenamed());
     assertEquals("y", y.getFinalName());
diff --git a/src/test/java/com/android/tools/r8/naming/b123068484/runner/Runner.java b/src/test/java/com/android/tools/r8/naming/b123068484/runner/Runner.java
index 84062c9..be7d821 100644
--- a/src/test/java/com/android/tools/r8/naming/b123068484/runner/Runner.java
+++ b/src/test/java/com/android/tools/r8/naming/b123068484/runner/Runner.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming.b123068484.runner;
 
-import com.android.tools.r8.naming.b123068484.data.PublicAbs;
 import com.android.tools.r8.naming.b123068484.data.Concrete1;
 import com.android.tools.r8.naming.b123068484.data.Concrete2;
+import com.android.tools.r8.naming.b123068484.data.PublicAbs;
 
 public class Runner {
   private static int counter = 0;
@@ -18,7 +18,7 @@
   }
 
   public static void main(String[] args) {
-    PublicAbs instance = create("Runner");
+    PublicAbs instance = create(System.currentTimeMillis() > 0 ? "Runner" : null);
     if (instance instanceof Concrete1) {
       System.out.println(((Concrete1) instance).strField);
     } else {
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldWithUnknownDynamicTypeIntoParameterMaybeNullTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldWithUnknownDynamicTypeIntoParameterMaybeNullTest.java
index 0d4e8d2..7917f8a 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldWithUnknownDynamicTypeIntoParameterMaybeNullTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldWithUnknownDynamicTypeIntoParameterMaybeNullTest.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.assertNotEquals;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
@@ -58,8 +58,7 @@
               MethodSubject testMethodSubject =
                   mainClassSubject.uniqueMethodWithOriginalName("test");
               assertThat(testMethodSubject, isPresent());
-              // TODO(b/296030319): Should be equals.
-              assertNotEquals(bClassSubject.asTypeSubject(), testMethodSubject.getParameter(0));
+              assertEquals(bClassSubject.asTypeSubject(), testMethodSubject.getParameter(0));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("B");
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldWithUnknownDynamicTypeIntoParameterTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldWithUnknownDynamicTypeIntoParameterTest.java
index 0298201..54b5560 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldWithUnknownDynamicTypeIntoParameterTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldWithUnknownDynamicTypeIntoParameterTest.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.assertNotEquals;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
@@ -54,8 +54,7 @@
               MethodSubject testMethodSubject =
                   mainClassSubject.uniqueMethodWithOriginalName("test");
               assertThat(testMethodSubject, isPresent());
-              // TODO(b/296030319): Should be equals.
-              assertNotEquals(bClassSubject.asTypeSubject(), testMethodSubject.getParameter(0));
+              assertEquals(bClassSubject.asTypeSubject(), testMethodSubject.getParameter(0));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("B");
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToLibraryOrClasspathMethodTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToLibraryOrClasspathMethodTest.java
index 52b3beb..e1b8630 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToLibraryOrClasspathMethodTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToLibraryOrClasspathMethodTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.optimize.argumentpropagation;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
@@ -101,9 +101,7 @@
         .run(parameters.getRuntime(), TestClass.class)
         .inspect(
             inspector -> {
-              assertThat(
-                  inspector.clazz(Delegate.class).method("void", "libraryMethod", "boolean"),
-                  isPresent());
+              assertThat(inspector.clazz(Delegate.class), isAbsent());
               // Check that boolean argument to libraryMethod was removed for AnotherProgramClass.
               inspector
                   .clazz(AnotherProgramClass.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
index d79c473..56e7b82 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
@@ -161,7 +161,8 @@
   public static class KeptClass<T> {
 
     private T keptField;
-    private T notKeptField;
+
+    @NoFieldTypeStrengthening private T notKeptField;
 
     @SuppressWarnings("unchecked")
     public <R> R keptMethod(T t) {