Compute dynamic type for each field

Change-Id: I9d622408f08ebeb672d75bd1fde1935ba8d235a4
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index e21fbf4..1f13804 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepClassInfo;
+import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.KeepMethodInfo;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -533,6 +534,10 @@
     return getKeepInfo().getClassInfo(clazz);
   }
 
+  public KeepFieldInfo getKeepInfo(ProgramField field) {
+    return getKeepInfo().getFieldInfo(field);
+  }
+
   public KeepMethodInfo getKeepInfo(ProgramMethod method) {
     return getKeepInfo().getMethodInfo(method);
   }
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 b3e8f74..c8a9046 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -55,7 +55,11 @@
   }
 
   public TypeElement toTypeElement(AppView<?> appView) {
-    return TypeElement.fromDexType(this, Nullability.maybeNull(), appView);
+    return toTypeElement(appView, Nullability.maybeNull());
+  }
+
+  public TypeElement toTypeElement(AppView<?> appView, Nullability nullability) {
+    return TypeElement.fromDexType(this, nullability, appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index 150f206..e5a1aec 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -85,8 +85,7 @@
               resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField();
           if (field != null) {
             if (fieldAssignmentTracker != null) {
-              fieldAssignmentTracker.recordFieldAccess(
-                  fieldInstruction, field.getDefinition(), code.context());
+              fieldAssignmentTracker.recordFieldAccess(fieldInstruction, field, code.context());
             }
             if (fieldBitAccessAnalysis != null) {
               fieldBitAccessAnalysis.recordFieldAccess(
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 70c8519..957c20e 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
@@ -4,19 +4,30 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
+import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteArrayTypeFieldState;
+import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteClassTypeFieldState;
+import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcretePrimitiveTypeFieldState;
+import com.android.tools.r8.ir.analysis.fieldaccess.state.FieldState;
+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.Nullability;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.BottomValue;
 import com.android.tools.r8.ir.analysis.value.NonConstantNumberValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -30,9 +41,10 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
@@ -40,13 +52,14 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
 public class FieldAssignmentTracker {
 
+  private final AbstractValueFactory abstractValueFactory;
   private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory dexItemFactory;
 
   // A field access graph with edges from methods to the fields that they access. Edges are removed
   // from the graph as we process methods, such that we can conclude that all field writes have been
@@ -58,14 +71,17 @@
   // sites have been seen when a class no longer has any incoming edges.
   private final ObjectAllocationGraph objectAllocationGraph;
 
-  // The set of fields that may store a non-zero value.
-  private final Set<DexEncodedField> nonZeroFields = Sets.newConcurrentHashSet();
+  // Information about the fields in the program. If a field is not a key in the map then no writes
+  // has been seen to the field.
+  private final Map<DexEncodedField, FieldState> fieldStates = new ConcurrentHashMap<>();
 
   private final Map<DexProgramClass, Map<DexEncodedField, AbstractValue>>
       abstractInstanceFieldValues = new ConcurrentHashMap<>();
 
   FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
+    this.abstractValueFactory = appView.abstractValueFactory();
     this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
     this.fieldAccessGraph = new FieldAccessGraph();
     this.objectAllocationGraph = new ObjectAllocationGraph();
   }
@@ -109,33 +125,109 @@
           }
           abstractInstanceFieldValues.put(clazz, abstractInstanceFieldValuesForClass);
         });
-  }
-
-  private boolean isAlwaysZero(DexEncodedField field) {
-    return !appView.appInfo().isPinned(field.getReference()) && !nonZeroFields.contains(field);
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachProgramField(
+          field -> {
+            FieldAccessInfo accessInfo = fieldAccessInfos.get(field.getReference());
+            KeepFieldInfo keepInfo = appView.getKeepInfo(field);
+            if (keepInfo.isPinned(appView.options()) || accessInfo.isWrittenFromMethodHandle()) {
+              fieldStates.put(field.getDefinition(), FieldState.unknown());
+            }
+          });
+    }
   }
 
   void acceptClassInitializerDefaultsResult(
       ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     classInitializerDefaultsResult.forEachOptimizedField(
         (field, value) -> {
-          if (!value.isDefault(field.getReference().type)) {
-            nonZeroFields.add(field);
+          DexType fieldType = field.getType();
+          if (value.isDefault(field.getType())) {
+            return;
           }
+          assert fieldType.isClassType() || fieldType.isPrimitiveType();
+          fieldStates.compute(
+              field,
+              (f, fieldState) -> {
+                if (fieldState == null) {
+                  AbstractValue abstractValue = value.toAbstractValue(abstractValueFactory);
+                  if (fieldType.isClassType()) {
+                    assert abstractValue.isSingleStringValue()
+                        || abstractValue.isSingleDexItemBasedStringValue();
+                    ClassTypeElement nonNullableStringType =
+                        dexItemFactory
+                            .stringType
+                            .toTypeElement(appView, definitelyNotNull())
+                            .asClassType();
+                    return ConcreteClassTypeFieldState.create(
+                        abstractValue, DynamicType.createExact(nonNullableStringType));
+                  } else {
+                    assert fieldType.isPrimitiveType();
+                    return ConcretePrimitiveTypeFieldState.create(abstractValue);
+                  }
+                }
+                // If the field is already assigned outside the class initializer then just give up.
+                return FieldState.unknown();
+              });
         });
   }
 
-  void recordFieldAccess(
-      FieldInstruction instruction, DexEncodedField field, ProgramMethod context) {
+  void recordFieldAccess(FieldInstruction instruction, ProgramField field, ProgramMethod context) {
     if (instruction.isFieldPut()) {
       recordFieldPut(field, instruction.value(), context);
     }
   }
 
-  private void recordFieldPut(DexEncodedField field, Value value, ProgramMethod context) {
-    if (!value.isZero()) {
-      nonZeroFields.add(field);
-    }
+  private void recordFieldPut(ProgramField field, Value value, ProgramMethod context) {
+    // For now only attempt to prove that fields are definitely null. In order to prove a single
+    // value for fields that are not definitely null, we need to prove that the given field is never
+    // read before it is written.
+    AbstractValue abstractValue =
+        value.isZero() ? abstractValueFactory.createZeroValue() : AbstractValue.unknown();
+    fieldStates.compute(
+        field.getDefinition(),
+        (f, fieldState) -> {
+          if (fieldState == null || fieldState.isBottom()) {
+            DexType fieldType = field.getType();
+            if (fieldType.isArrayType()) {
+              return ConcreteArrayTypeFieldState.create(abstractValue);
+            }
+            if (fieldType.isPrimitiveType()) {
+              return ConcretePrimitiveTypeFieldState.create(abstractValue);
+            }
+            assert fieldType.isClassType();
+            DynamicType dynamicType =
+                fieldType.isArrayType()
+                    ? DynamicType.unknown()
+                    : WideningUtils.widenDynamicNonReceiverType(
+                        appView,
+                        value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
+                        field.getType());
+            return ConcreteClassTypeFieldState.create(abstractValue, dynamicType);
+          }
+
+          if (fieldState.isUnknown()) {
+            return fieldState;
+          }
+
+          assert fieldState.isConcrete();
+
+          if (fieldState.isArray()) {
+            ConcreteArrayTypeFieldState arrayFieldState = fieldState.asArray();
+            return arrayFieldState.mutableJoin(appView, abstractValue);
+          }
+
+          if (fieldState.isPrimitive()) {
+            ConcretePrimitiveTypeFieldState primitiveFieldState = fieldState.asPrimitive();
+            return primitiveFieldState.mutableJoin(abstractValue, abstractValueFactory);
+          }
+
+          assert fieldState.isClass();
+
+          ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
+          return classFieldState.mutableJoin(
+              appView, abstractValue, value.getDynamicType(appView), field);
+        });
   }
 
   void recordAllocationSite(NewInstance instruction, DexProgramClass clazz, ProgramMethod context) {
@@ -146,7 +238,7 @@
       return;
     }
 
-    InvokeDirect invoke = instruction.getUniqueConstructorInvoke(appView.dexItemFactory());
+    InvokeDirect invoke = instruction.getUniqueConstructorInvoke(dexItemFactory);
     if (invoke == null) {
       // We just lost track.
       abstractInstanceFieldValues.remove(clazz);
@@ -238,27 +330,33 @@
   }
 
   private void recordAllFieldPutsProcessed(
-      DexEncodedField field, ProgramMethod context, OptimizationFeedbackDelayed feedback) {
-    DexProgramClass clazz = asProgramClassOrNull(appView.definitionForHolder(field, context));
-    if (clazz == null) {
-      assert false;
-      return;
+      ProgramField field, ProgramMethod context, OptimizationFeedbackDelayed feedback) {
+    FieldState fieldState = fieldStates.getOrDefault(field.getDefinition(), FieldState.bottom());
+    AbstractValue abstractValue = fieldState.getAbstractValue(appView.abstractValueFactory());
+    if (abstractValue.isNonTrivial()) {
+      feedback.recordFieldHasAbstractValue(field.getDefinition(), appView, abstractValue);
     }
 
-    if (isAlwaysZero(field)) {
-      feedback.recordFieldHasAbstractValue(
-          field, appView, appView.abstractValueFactory().createSingleNumberValue(0));
+    if (fieldState.isClass() && !field.getOptimizationInfo().hasDynamicUpperBoundType()) {
+      ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
+      DynamicType dynamicType = classFieldState.getDynamicType();
+      if (!dynamicType.isUnknown()) {
+        assert WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, field.getType())
+            == dynamicType;
+        feedback.markFieldHasDynamicType(field, dynamicType);
+      }
     }
 
-    if (!field.isStatic()) {
-      recordAllInstanceFieldPutsProcessed(clazz, field, feedback);
+    if (!field.getAccessFlags().isStatic()) {
+      recordAllInstanceFieldPutsProcessed(field, feedback);
     }
   }
 
   private void recordAllInstanceFieldPutsProcessed(
-      DexProgramClass clazz, DexEncodedField field, OptimizationFeedbackDelayed feedback) {
+      ProgramField field, OptimizationFeedbackDelayed feedback) {
     if (appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
       AbstractValue abstractValue = BottomValue.getInstance();
+      DexProgramClass clazz = field.getHolder();
       for (DexEncodedMethod method : clazz.directMethods(DexEncodedMethod::isInstanceInitializer)) {
         InstanceFieldInitializationInfo fieldInitializationInfo =
             method
@@ -290,7 +388,7 @@
       assert !abstractValue.isBottom();
 
       if (!abstractValue.isUnknown()) {
-        feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+        feedback.recordFieldHasAbstractValue(field.getDefinition(), appView, abstractValue);
       }
     }
   }
@@ -334,8 +432,7 @@
   static class FieldAccessGraph {
 
     // The fields written by each method.
-    private final Map<DexEncodedMethod, List<DexEncodedField>> fieldWrites =
-        new IdentityHashMap<>();
+    private final Map<DexEncodedMethod, List<ProgramField>> fieldWrites = new IdentityHashMap<>();
 
     // The number of writes that have not yet been processed per field.
     private final Reference2IntMap<DexEncodedField> pendingFieldWrites =
@@ -348,8 +445,7 @@
           appView.appInfo().getFieldAccessInfoCollection();
       fieldAccessInfoCollection.forEach(
           info -> {
-            DexEncodedField field =
-                appView.appInfo().resolveField(info.getField()).getResolvedField();
+            ProgramField field = appView.appInfo().resolveField(info.getField()).getProgramField();
             if (field == null) {
               return;
             }
@@ -359,18 +455,18 @@
                       fieldWrites
                           .computeIfAbsent(context.getDefinition(), ignore -> new ArrayList<>())
                           .add(field));
-              pendingFieldWrites.put(field, info.getNumberOfWriteContexts());
+              pendingFieldWrites.put(field.getDefinition(), info.getNumberOfWriteContexts());
             }
           });
     }
 
-    void markProcessed(ProgramMethod method, Consumer<DexEncodedField> allWritesSeenConsumer) {
-      List<DexEncodedField> fieldWritesInMethod = fieldWrites.get(method.getDefinition());
+    void markProcessed(ProgramMethod method, Consumer<ProgramField> allWritesSeenConsumer) {
+      List<ProgramField> fieldWritesInMethod = fieldWrites.get(method.getDefinition());
       if (fieldWritesInMethod != null) {
-        for (DexEncodedField field : fieldWritesInMethod) {
-          int numberOfPendingFieldWrites = pendingFieldWrites.removeInt(field) - 1;
+        for (ProgramField field : fieldWritesInMethod) {
+          int numberOfPendingFieldWrites = pendingFieldWrites.removeInt(field.getDefinition()) - 1;
           if (numberOfPendingFieldWrites > 0) {
-            pendingFieldWrites.put(field, numberOfPendingFieldWrites);
+            pendingFieldWrites.put(field.getDefinition(), numberOfPendingFieldWrites);
           } else {
             allWritesSeenConsumer.accept(field);
           }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index f25c73b..788e9cc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -261,6 +261,8 @@
       assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
           : "At " + this + System.lineSeparator() + outType + " != " + inType;
     } else {
+      assert hasDynamicTypeAssumption();
+      assert !src().isConstNumber();
       assert outType.equals(inType)
           : "At " + this + System.lineSeparator() + outType + " != " + inType;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 38e86e1..36ec641 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -132,7 +132,9 @@
       if (receiverLowerBoundType != null) {
         DexType refinedReceiverType =
             TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this);
-        assert receiverLowerBoundType.getClassType() == refinedReceiverType
+        assert appViewWithLiveness
+                    .appInfo()
+                    .isSubtype(receiverLowerBoundType.getClassType(), refinedReceiverType)
                 || appView.options().testing.allowTypeErrors
                 || receiver.getDynamicUpperBoundType(appViewWithLiveness).isNullType()
                 || receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index c6070c5..d91c889 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -6,7 +6,9 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.ProgramField;
 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.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -19,6 +21,15 @@
 
   void markFieldAsPropagated(DexEncodedField field);
 
+  default void markFieldHasDynamicType(ProgramField field, DynamicType dynamicType) {
+    TypeElement dynamicUpperBoundType = dynamicType.getDynamicUpperBoundType();
+    markFieldHasDynamicUpperBoundType(field.getDefinition(), dynamicUpperBoundType);
+    if (dynamicType.hasDynamicLowerBoundType()) {
+      ClassTypeElement dynamicLowerBoundType = dynamicType.getDynamicLowerBoundType();
+      markFieldHasDynamicLowerBoundType(field.getDefinition(), dynamicLowerBoundType);
+    }
+  }
+
   void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeElement type);
 
   void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeElement type);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
index f8abfc5..70057fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
@@ -62,7 +62,7 @@
         Assume assumeInstruction = user.asAssume();
         assumeInstruction.unsetDynamicTypeAssumption();
         if (!assumeInstruction.hasNonNullAssumption()) {
-          assumeInstruction.unsetDynamicTypeAssumption();
+          markForRemoval(assumeInstruction);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 206c455..755f334 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -411,7 +411,7 @@
       // Verify that the optimization info is consistent with the static value.
       assert definition.getOptimizationInfo().getAbstractValue().isUnknown()
           || !definition.hasExplicitStaticValue()
-          || abstractValue == definition.getOptimizationInfo().getAbstractValue();
+          || abstractValue.equals(definition.getOptimizationInfo().getAbstractValue());
     } else {
       // This is guaranteed to read the default value of the field.
       abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index ef3795d..442cb96 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -226,8 +226,9 @@
         }
 
         // Restore normality.
+        assumeRemover.removeMarkedInstructions();
         code.removeAllDeadAndTrivialPhis(affectedValues);
-        assumeRemover.removeMarkedInstructions().finish();
+        assumeRemover.finish();
         assert code.isConsistentSSA();
         rootsIterator.remove();
         repeat = true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 8f42838..240beb2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -27,6 +27,10 @@
 
   public abstract ClassTypeElement getDynamicLowerBoundType();
 
+  public final boolean hasDynamicUpperBoundType() {
+    return getDynamicUpperBoundType() != null;
+  }
+
   public abstract TypeElement getDynamicUpperBoundType();
 
   public ClassTypeElement getExactClassType(AppView<AppInfoWithLiveness> appView) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
index ef40285..c5d846e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.info.field;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
@@ -37,6 +38,10 @@
 
   public abstract InstanceFieldInitializationInfo get(DexEncodedField field);
 
+  public final InstanceFieldInitializationInfo get(DexClassAndField field) {
+    return get(field.getDefinition());
+  }
+
   public abstract boolean isEmpty();
 
   public abstract InstanceFieldInitializationInfoCollection fixupAfterParametersChanged(
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 61c9077..3bd0c98 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
@@ -1060,6 +1061,10 @@
         && !fieldAccessInfo.isWrittenOutside(method);
   }
 
+  public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexClassAndField field) {
+    return isInstanceFieldWrittenOnlyInInstanceInitializers(field.getDefinition());
+  }
+
   public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexEncodedField field) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 0e373e9..f1498b6 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.AlwaysInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -51,6 +52,11 @@
             "  synthetic void registerObserver(...);",
             "}")
         .allowAccessModification()
+        .addAlwaysInliningAnnotations()
+        .addKeepRules(
+            "-alwaysinline class * { @"
+                + AlwaysInline.class.getTypeName()
+                + " !synthetic <methods>; }")
         .enableNeverClassInliningAnnotations()
         // TODO(b/120764902): MemberSubject.getOriginalName() is not working without the @NeverMerge
         //  annotation on DataAdapter.Observer.
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
index e6eceb7..b99ee02 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.bridgeremoval.bridgestokeep;
 
+import com.android.tools.r8.AlwaysInline;
 import com.android.tools.r8.bridgeremoval.bridgestokeep.ObservableList.Observer;
 import java.util.ArrayList;
 import java.util.List;
@@ -13,6 +14,7 @@
 
   private List<O> observers = new ArrayList<>();
 
+  @AlwaysInline
   @Override
   public void registerObserver(O observer) {
     if (observer != null && observers != null && !observers.contains(observer)) {