Towards using a new abstraction for definitely set/unset bits

This cleans up the joining of abstract values by introducing a new AbstractValueJoiner class that encapsulates all logic related to joining (including which and when to use specific abstractions).

This also introduces a new DefiniteBitsNumberValue abstraction that can be used for integer values. Changing the join of abstract values to use this new abstraction is left for future work.

Bug: b/196017578
Fixes: b/196321452
Change-Id: I5fba6fdc8d04c185d9b932c08b354c0eff5f97a2
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 5a20e3d..06deed3 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -26,6 +26,8 @@
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.AbstractValueJoiner.AbstractValueFieldJoiner;
+import com.android.tools.r8.ir.analysis.value.AbstractValueJoiner.AbstractValueParameterJoiner;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
@@ -100,6 +102,8 @@
   private KeepInfoCollection keepInfo = null;
 
   private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
+  private final AbstractValueFieldJoiner abstractValueFieldJoiner;
+  private final AbstractValueParameterJoiner abstractValueParameterJoiner;
   private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory =
       new InstanceFieldInitializationInfoFactory();
   private final SimpleInliningConstraintFactory simpleInliningConstraintFactory =
@@ -170,13 +174,20 @@
     this.context =
         timing.time(
             "Compilation context", () -> CompilationContext.createInitialContext(options()));
+    this.wholeProgramOptimizations = wholeProgramOptimizations;
+    if (enableWholeProgramOptimizations()) {
+      abstractValueFieldJoiner = new AbstractValueFieldJoiner(withClassHierarchy());
+      abstractValueParameterJoiner = new AbstractValueParameterJoiner(withClassHierarchy());
+    } else {
+      abstractValueFieldJoiner = null;
+      abstractValueParameterJoiner = null;
+    }
     this.artProfileCollection = artProfileCollection;
     this.startupProfile = startupProfile;
     this.dontWarnConfiguration =
         timing.time(
             "Dont warn config",
             () -> DontWarnConfiguration.create(options().getProguardConfiguration()));
-    this.wholeProgramOptimizations = wholeProgramOptimizations;
     this.initClassLens = timing.time("Init class lens", InitClassLens::getThrowingInstance);
     this.typeRewriter = mapper;
     timing.begin("Create argument propagator");
@@ -314,6 +325,14 @@
     return abstractValueFactory;
   }
 
+  public AbstractValueFieldJoiner getAbstractValueFieldJoiner() {
+    return abstractValueFieldJoiner;
+  }
+
+  public AbstractValueParameterJoiner getAbstractValueParameterJoiner() {
+    return abstractValueParameterJoiner;
+  }
+
   public InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory() {
     return instanceFieldInitializationInfoFactory;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 1aa866f..fb50d0e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -415,6 +415,18 @@
         .traverse((field, value) -> fn.apply(field.asProgramField(), value), initialValue);
   }
 
+  public TraversalContinuation<?, ?> traverseProgramInstanceFields(
+      Function<? super ProgramField, TraversalContinuation<?, ?>> fn) {
+    return getFieldCollection().traverseInstanceFields(field -> fn.apply(field.asProgramField()));
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverseProgramInstanceFields(
+      BiFunction<? super ProgramField, CT, TraversalContinuation<BT, CT>> fn, CT initialValue) {
+    return getFieldCollection()
+        .traverseInstanceFields(
+            (field, value) -> fn.apply(field.asProgramField(), value), initialValue);
+  }
+
   public TraversalContinuation<?, ?> traverseProgramMethods(
       Function<? super ProgramMethod, TraversalContinuation<?, ?>> fn) {
     return getMethodCollection().traverse(method -> fn.apply(new ProgramMethod(this, method)));
diff --git a/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java b/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java
index dee7446..ec0758a 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java
@@ -72,15 +72,61 @@
   @Override
   <BT, CT> TraversalContinuation<BT, CT> traverse(
       DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
-    TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
-    for (DexEncodedField definition : staticFields) {
-      DexClassAndField field = DexClassAndField.create(holder, definition);
-      traversalContinuation = fn.apply(field);
-      if (traversalContinuation.shouldBreak()) {
-        return traversalContinuation;
-      }
+    TraversalContinuation<BT, CT> traversalContinuation = traverseStaticFields(holder, fn);
+    if (traversalContinuation.shouldBreak()) {
+      return traversalContinuation;
     }
-    for (DexEncodedField definition : instanceFields) {
+    return traverseInstanceFields(holder, fn);
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverse(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    TraversalContinuation<BT, CT> traversalContinuation =
+        traverseStaticFields(holder, fn, initialValue);
+    if (traversalContinuation.shouldBreak()) {
+      return traversalContinuation;
+    }
+    return traverseInstanceFields(
+        holder, fn, traversalContinuation.asContinue().getValueOrDefault(null));
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+      DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    return traverseInstanceOrStaticFields(holder, instanceFields, fn);
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return traverseInstanceOrStaticFields(holder, instanceFields, fn, initialValue);
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+      DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    return traverseInstanceOrStaticFields(holder, staticFields, fn);
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return traverseInstanceOrStaticFields(holder, staticFields, fn, initialValue);
+  }
+
+  private static <BT, CT> TraversalContinuation<BT, CT> traverseInstanceOrStaticFields(
+      DexClass holder,
+      DexEncodedField[] fields,
+      Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
+    for (DexEncodedField definition : fields) {
       DexClassAndField field = DexClassAndField.create(holder, definition);
       traversalContinuation = fn.apply(field);
       if (traversalContinuation.shouldBreak()) {
@@ -90,21 +136,14 @@
     return traversalContinuation;
   }
 
-  @Override
-  <BT, CT> TraversalContinuation<BT, CT> traverse(
+  private static <BT, CT> TraversalContinuation<BT, CT> traverseInstanceOrStaticFields(
       DexClass holder,
+      DexEncodedField[] fields,
       BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
       CT initialValue) {
     TraversalContinuation<BT, CT> traversalContinuation =
         TraversalContinuation.doContinue(initialValue);
-    for (DexEncodedField definition : staticFields) {
-      DexClassAndField field = DexClassAndField.create(holder, definition);
-      traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
-      if (traversalContinuation.shouldBreak()) {
-        return traversalContinuation;
-      }
-    }
-    for (DexEncodedField definition : instanceFields) {
+    for (DexEncodedField definition : fields) {
       DexClassAndField field = DexClassAndField.create(holder, definition);
       traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
       if (traversalContinuation.shouldBreak()) {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldCollection.java b/src/main/java/com/android/tools/r8/graph/FieldCollection.java
index 729b238..9b3c09b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldCollection.java
@@ -73,6 +73,28 @@
     return backing.traverse(holder, fn, initialValue);
   }
 
+  public <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+      Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    return backing.traverseInstanceFields(holder, fn);
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return backing.traverseInstanceFields(holder, fn, initialValue);
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+      Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    return backing.traverseStaticFields(holder, fn);
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return backing.traverseStaticFields(holder, fn, initialValue);
+  }
+
   public boolean verify() {
     forEachField(
         field -> {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java
index 522748e..cce38fd 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java
@@ -34,6 +34,22 @@
       BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
       CT initialValue);
 
+  abstract <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+      DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn);
+
+  abstract <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue);
+
+  abstract <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+      DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn);
+
+  abstract <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue);
+
   // Collection methods.
 
   abstract int size();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java b/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java
index 6b74673..a6d9b57 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap;
@@ -49,15 +51,7 @@
   @Override
   <BT, CT> TraversalContinuation<BT, CT> traverse(
       DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
-    TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
-    for (DexEncodedField definition : fieldMap.values()) {
-      DexClassAndField field = DexClassAndField.create(holder, definition);
-      traversalContinuation = fn.apply(field);
-      if (traversalContinuation.shouldBreak()) {
-        return traversalContinuation;
-      }
-    }
-    return traversalContinuation;
+    return traverseFieldsThatMatches(holder, fn, alwaysTrue());
   }
 
   @Override
@@ -65,13 +59,68 @@
       DexClass holder,
       BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
       CT initialValue) {
+    return traverseFieldsThatMatches(holder, fn, alwaysTrue(), initialValue);
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+      DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    return traverseFieldsThatMatches(holder, fn, DexEncodedField::isInstance);
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return traverseFieldsThatMatches(holder, fn, DexEncodedField::isInstance, initialValue);
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+      DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    return traverseFieldsThatMatches(holder, fn, DexEncodedField::isStatic);
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return traverseFieldsThatMatches(holder, fn, DexEncodedField::isStatic, initialValue);
+  }
+
+  private <BT, CT> TraversalContinuation<BT, CT> traverseFieldsThatMatches(
+      DexClass holder,
+      Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn,
+      Predicate<? super DexEncodedField> predicate) {
+    TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
+    for (DexEncodedField definition : fieldMap.values()) {
+      if (predicate.test(definition)) {
+        DexClassAndField field = DexClassAndField.create(holder, definition);
+        traversalContinuation = fn.apply(field);
+        if (traversalContinuation.shouldBreak()) {
+          return traversalContinuation;
+        }
+      }
+    }
+    return traversalContinuation;
+  }
+
+  private <BT, CT> TraversalContinuation<BT, CT> traverseFieldsThatMatches(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      Predicate<? super DexEncodedField> predicate,
+      CT initialValue) {
     TraversalContinuation<BT, CT> traversalContinuation =
         TraversalContinuation.doContinue(initialValue);
     for (DexEncodedField definition : fieldMap.values()) {
-      DexClassAndField field = DexClassAndField.create(holder, definition);
-      traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
-      if (traversalContinuation.shouldBreak()) {
-        return traversalContinuation;
+      if (predicate.test(definition)) {
+        DexClassAndField field = DexClassAndField.create(holder, definition);
+        traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
+        if (traversalContinuation.shouldBreak()) {
+          return traversalContinuation;
+        }
       }
     }
     return traversalContinuation;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
index bd83a89..6eecb7a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
@@ -7,9 +7,14 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.ProgramField;
 
 public class HorizontalClassMergerUtils {
 
+  public static boolean isClassIdField(AppView<?> appView, ProgramField field) {
+    return isClassIdField(appView, field.getDefinition());
+  }
+
   public static boolean isClassIdField(AppView<?> appView, DexEncodedField field) {
     DexField classIdField = appView.dexItemFactory().objectMembers.classIdField;
     if (field.isD8R8Synthesized() && field.getType().isIntType()) {
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 d871616..e6ac132 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,7 +85,7 @@
             appView.appInfo().resolveField(fieldInstruction.getField()).getProgramField();
         if (field != null) {
           if (fieldAssignmentTracker != null) {
-            fieldAssignmentTracker.recordFieldAccess(fieldInstruction, field, code.context());
+            fieldAssignmentTracker.recordFieldAccess(fieldInstruction, field);
           }
           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 0781787..0f47d7e 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
@@ -45,12 +45,13 @@
 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.TraversalContinuation;
+import com.android.tools.r8.utils.collections.ProgramFieldMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -76,7 +77,7 @@
   // has been seen to the field.
   private final Map<DexEncodedField, FieldState> fieldStates = new ConcurrentHashMap<>();
 
-  private final Map<DexProgramClass, Map<DexEncodedField, AbstractValue>>
+  private final Map<DexProgramClass, ProgramFieldMap<AbstractValue>>
       abstractFinalInstanceFieldValues = new ConcurrentHashMap<>();
 
   FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
@@ -116,15 +117,14 @@
             // No instance fields to track.
             return;
           }
-          Map<DexEncodedField, AbstractValue> abstractFinalInstanceFieldValuesForClass =
-              new IdentityHashMap<>();
+          ProgramFieldMap<AbstractValue> abstractFinalInstanceFieldValuesForClass =
+              ProgramFieldMap.create();
           clazz.forEachProgramInstanceField(
               field -> {
                 if (field.isFinalOrEffectivelyFinal(appView)) {
                   FieldAccessInfo fieldAccessInfo = fieldAccessInfos.get(field.getReference());
                   if (fieldAccessInfo != null && !fieldAccessInfo.hasReflectiveAccess()) {
-                    abstractFinalInstanceFieldValuesForClass.put(
-                        field.getDefinition(), BottomValue.getInstance());
+                    abstractFinalInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
                   }
                 }
               });
@@ -185,13 +185,13 @@
         });
   }
 
-  void recordFieldAccess(FieldInstruction instruction, ProgramField field, ProgramMethod context) {
+  void recordFieldAccess(FieldInstruction instruction, ProgramField field) {
     if (instruction.isFieldPut()) {
-      recordFieldPut(field, instruction.value(), context);
+      recordFieldPut(field, instruction.value());
     }
   }
 
-  private void recordFieldPut(ProgramField field, Value value, ProgramMethod context) {
+  private void recordFieldPut(ProgramField field, Value value) {
     // 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.
@@ -225,12 +225,12 @@
 
           if (fieldState.isArray()) {
             ConcreteArrayTypeFieldState arrayFieldState = fieldState.asArray();
-            return arrayFieldState.mutableJoin(appView, abstractValue);
+            return arrayFieldState.mutableJoin(appView, field, abstractValue);
           }
 
           if (fieldState.isPrimitive()) {
             ConcretePrimitiveTypeFieldState primitiveFieldState = fieldState.asPrimitive();
-            return primitiveFieldState.mutableJoin(abstractValue, abstractValueFactory);
+            return primitiveFieldState.mutableJoin(appView, field, abstractValue);
           }
 
           assert fieldState.isClass();
@@ -242,7 +242,7 @@
   }
 
   void recordAllocationSite(NewInstance instruction, DexProgramClass clazz, ProgramMethod context) {
-    Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+    ProgramFieldMap<AbstractValue> abstractInstanceFieldValuesForClass =
         abstractFinalInstanceFieldValues.get(clazz);
     if (abstractInstanceFieldValuesForClass == null) {
       // We are not tracking the value of any of clazz' instance fields.
@@ -273,75 +273,59 @@
     // Synchronize on the lattice element (abstractInstanceFieldValuesForClass) in case we process
     // another allocation site of `clazz` concurrently.
     synchronized (abstractInstanceFieldValuesForClass) {
-      Iterator<Map.Entry<DexEncodedField, AbstractValue>> iterator =
-          abstractInstanceFieldValuesForClass.entrySet().iterator();
-      while (iterator.hasNext()) {
-        Map.Entry<DexEncodedField, AbstractValue> entry = iterator.next();
-        DexEncodedField field = entry.getKey();
-        AbstractValue abstractValue = entry.getValue();
-
-        // The power set lattice is an expensive abstraction, so use it with caution.
-        boolean isClassIdField = HorizontalClassMergerUtils.isClassIdField(appView, field);
-
-        InstanceFieldInitializationInfo initializationInfo =
-            initializationInfoCollection.get(field);
-        if (initializationInfo.isArgumentInitializationInfo()) {
-          InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
-              initializationInfo.asArgumentInitializationInfo();
-          Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
-          AbstractValue argumentAbstractValue = argument.getAbstractValue(appView, context);
-          abstractValue =
-              abstractValue.join(
-                  argumentAbstractValue,
-                  appView.abstractValueFactory(),
-                  field.getType().isReferenceType(),
-                  isClassIdField);
-          assert !abstractValue.isBottom();
-        } else if (initializationInfo.isSingleValue()) {
-          SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue();
-          abstractValue =
-              abstractValue.join(
-                  singleValueInitializationInfo,
-                  appView.abstractValueFactory(),
-                  field.getType().isReferenceType(),
-                  isClassIdField);
-        } else if (initializationInfo.isTypeInitializationInfo()) {
-          // TODO(b/149732532): Not handled, for now.
-          abstractValue = UnknownValue.getInstance();
-        } else {
-          assert initializationInfo.isUnknown();
-          abstractValue = UnknownValue.getInstance();
-        }
-
-        assert !abstractValue.isBottom();
-
-        // When approximating the possible values for the $r8$classId fields from horizontal class
-        // merging, give up if the set of possible values equals the size of the merge group. In
-        // this case, the information is useless.
-        if (isClassIdField && abstractValue.isNonConstantNumberValue()) {
-          NonConstantNumberValue initialAbstractValue =
-              field.getOptimizationInfo().getAbstractValue().asNonConstantNumberValue();
-          if (initialAbstractValue != null) {
-            if (abstractValue.asNonConstantNumberValue().getAbstractionSize()
-                >= initialAbstractValue.getAbstractionSize()) {
+      abstractInstanceFieldValuesForClass.removeIf(
+          (field, abstractValue, entry) -> {
+            InstanceFieldInitializationInfo initializationInfo =
+                initializationInfoCollection.get(field);
+            if (initializationInfo.isArgumentInitializationInfo()) {
+              InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
+                  initializationInfo.asArgumentInitializationInfo();
+              Value argument =
+                  invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
+              AbstractValue argumentAbstractValue = argument.getAbstractValue(appView, context);
+              abstractValue =
+                  appView
+                      .getAbstractValueFieldJoiner()
+                      .join(abstractValue, argumentAbstractValue, field);
+            } else if (initializationInfo.isSingleValue()) {
+              SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue();
+              abstractValue =
+                  appView
+                      .getAbstractValueFieldJoiner()
+                      .join(abstractValue, singleValueInitializationInfo, field);
+            } else if (initializationInfo.isTypeInitializationInfo()) {
+              // TODO(b/149732532): Not handled, for now.
+              abstractValue = UnknownValue.getInstance();
+            } else {
+              assert initializationInfo.isUnknown();
               abstractValue = UnknownValue.getInstance();
             }
-          }
-        }
 
-        if (!abstractValue.isUnknown()) {
-          entry.setValue(abstractValue);
-          continue;
-        }
+            assert !abstractValue.isBottom();
 
-        // We just lost track for this field.
-        iterator.remove();
-      }
+            // When approximating the possible values for the $r8$classId fields from horizontal
+            // class
+            // merging, give up if the set of possible values equals the size of the merge group. In
+            // this case, the information is useless.
+            if (abstractValue.isNonConstantNumberValue()) {
+              assert HorizontalClassMergerUtils.isClassIdField(appView, field);
+              NonConstantNumberValue initialAbstractValue =
+                  field.getOptimizationInfo().getAbstractValue().asNonConstantNumberValue();
+              if (initialAbstractValue != null
+                  && abstractValue.asNonConstantNumberValue().getAbstractionSize()
+                      >= initialAbstractValue.getAbstractionSize()) {
+                abstractValue = UnknownValue.getInstance();
+              }
+            }
+
+            entry.setValue(abstractValue);
+            return abstractValue.isUnknown();
+          });
     }
   }
 
   private void recordAllFieldPutsProcessed(
-      ProgramField field, ProgramMethod context, OptimizationFeedbackDelayed feedback) {
+      ProgramField field, OptimizationFeedbackDelayed feedback) {
     FieldState fieldState = fieldStates.getOrDefault(field.getDefinition(), FieldState.bottom());
     AbstractValue abstractValue = fieldState.getAbstractValue(appView.abstractValueFactory());
     if (abstractValue.isNonTrivial()) {
@@ -384,10 +368,9 @@
                 .get(field);
         if (fieldInitializationInfo.isSingleValue()) {
           abstractValue =
-              abstractValue.join(
-                  fieldInitializationInfo.asSingleValue(),
-                  appView.abstractValueFactory(),
-                  field.getType());
+              appView
+                  .getAbstractValueFieldJoiner()
+                  .join(abstractValue, fieldInitializationInfo.asSingleValue(), field);
           if (abstractValue.isUnknown()) {
             break;
           }
@@ -413,24 +396,25 @@
 
   private void recordAllAllocationsSitesProcessed(
       DexProgramClass clazz, OptimizationFeedbackDelayed feedback) {
-    Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+    ProgramFieldMap<AbstractValue> abstractInstanceFieldValuesForClass =
         abstractFinalInstanceFieldValues.get(clazz);
     if (abstractInstanceFieldValuesForClass == null) {
       return;
     }
 
-    for (DexEncodedField field : clazz.instanceFields()) {
-      AbstractValue abstractValue =
-          abstractInstanceFieldValuesForClass.getOrDefault(field, UnknownValue.getInstance());
-      if (abstractValue.isBottom()) {
-        feedback.modifyAppInfoWithLiveness(modifier -> modifier.removeInstantiatedType(clazz));
-        break;
-      }
-      if (abstractValue.isUnknown()) {
-        continue;
-      }
-      feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
-    }
+    clazz.traverseProgramInstanceFields(
+        field -> {
+          AbstractValue abstractValue =
+              abstractInstanceFieldValuesForClass.getOrDefault(field, UnknownValue.getInstance());
+          if (abstractValue.isBottom()) {
+            feedback.modifyAppInfoWithLiveness(modifier -> modifier.removeInstantiatedType(clazz));
+            return TraversalContinuation.doBreak();
+          }
+          if (abstractValue.isNonTrivial()) {
+            feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+          }
+          return TraversalContinuation.doContinue();
+        });
   }
 
   public void waveDone(ProgramMethodSet wave, OptimizationFeedbackDelayed feedback) {
@@ -438,8 +422,7 @@
     // therefore important that the optimization info has been flushed in advance.
     assert feedback.noUpdatesLeft();
     for (ProgramMethod method : wave) {
-      fieldAccessGraph.markProcessed(
-          method, field -> recordAllFieldPutsProcessed(field, method, feedback));
+      fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
       objectAllocationGraph.markProcessed(
           method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java
index 1b82558..fef8935 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.fieldaccess.state;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -36,9 +37,10 @@
     return this;
   }
 
-  public FieldState mutableJoin(AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
+  public FieldState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ProgramField field, AbstractValue abstractValue) {
     this.abstractValue =
-        this.abstractValue.joinReference(abstractValue, appView.abstractValueFactory());
+        appView.getAbstractValueFieldJoiner().join(this.abstractValue, abstractValue, field);
     return isEffectivelyUnknown() ? unknown() : this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
index 3f51c25..a323ba6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
@@ -48,7 +48,7 @@
       ProgramField field) {
     assert field.getType().isClassType();
     this.abstractValue =
-        this.abstractValue.joinReference(abstractValue, appView.abstractValueFactory());
+        appView.getAbstractValueFieldJoiner().join(this.abstractValue, abstractValue, field);
     this.dynamicType =
         WideningUtils.widenDynamicNonReceiverType(
             appView, this.dynamicType.join(appView, dynamicType), field.getType());
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
index c2eb778..1f329f5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess.state;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 /** The information that we track for fields whose type is a primitive type. */
 public class ConcretePrimitiveTypeFieldState extends ConcreteFieldState {
@@ -38,11 +41,12 @@
   }
 
   public FieldState mutableJoin(
-      AbstractValue abstractValue, AbstractValueFactory abstractValueFactory) {
+      AppView<AppInfoWithLiveness> appView, ProgramField field, AbstractValue abstractValue) {
     if (abstractValue.isUnknown()) {
       return FieldState.unknown();
     }
-    this.abstractValue = this.abstractValue.joinPrimitive(abstractValue, abstractValueFactory);
+    this.abstractValue =
+        appView.getAbstractValueFieldJoiner().join(this.abstractValue, abstractValue, field);
     return isEffectivelyUnknown() ? unknown() : this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index c4e0a83..fccb291 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -176,71 +175,12 @@
     return null;
   }
 
-  public AbstractValue join(AbstractValue other, AbstractValueFactory factory, DexType type) {
-    return join(other, factory, type.isReferenceType(), false);
+  public boolean isDefiniteBitsNumberValue() {
+    return false;
   }
 
-  public AbstractValue joinPrimitive(AbstractValue other, AbstractValueFactory factory) {
-    return join(other, factory, false, false);
-  }
-
-  public AbstractValue joinReference(AbstractValue other, AbstractValueFactory factory) {
-    return join(other, factory, true, false);
-  }
-
-  // TODO(b/196321452): Clean this up, in particular, replace the "allow" parameters by a
-  //  configuration object.
-  public AbstractValue join(
-      AbstractValue other,
-      AbstractValueFactory factory,
-      boolean allowNullOrAbstractValue,
-      boolean allowNonConstantNumbers) {
-    if (isBottom() || other.isUnknown()) {
-      return other;
-    }
-    if (isUnknown() || other.isBottom()) {
-      return this;
-    }
-    if (equals(other)) {
-      return this;
-    }
-    if (allowNullOrAbstractValue) {
-      if (isNull()) {
-        return NullOrAbstractValue.create(other);
-      }
-      if (other.isNull()) {
-        return NullOrAbstractValue.create(this);
-      }
-      if (isNullOrAbstractValue() && asNullOrAbstractValue().getNonNullValue().equals(other)) {
-        return this;
-      }
-      if (other.isNullOrAbstractValue()
-          && other.asNullOrAbstractValue().getNonNullValue().equals(this)) {
-        return other;
-      }
-      return unknown();
-    }
-    assert !isNullOrAbstractValue();
-    assert !other.isNullOrAbstractValue();
-    if (allowNonConstantNumbers
-        && isConstantOrNonConstantNumberValue()
-        && other.isConstantOrNonConstantNumberValue()) {
-      NumberFromSetValue.Builder numberFromSetValueBuilder;
-      if (isSingleNumberValue()) {
-        numberFromSetValueBuilder = NumberFromSetValue.builder(asSingleNumberValue());
-      } else {
-        assert isNumberFromSetValue();
-        numberFromSetValueBuilder = asNumberFromSetValue().instanceBuilder();
-      }
-      if (other.isSingleNumberValue()) {
-        numberFromSetValueBuilder.addInt(other.asSingleNumberValue().getIntValue());
-      } else {
-        assert other.isNumberFromSetValue();
-        numberFromSetValueBuilder.addInts(other.asNumberFromSetValue());
-      }
-      return numberFromSetValueBuilder.build(factory);
-    }
-    return unknown();
+  public DefiniteBitsNumberValue asDefiniteBitsNumberValue() {
+    return null;
   }
 
   public abstract AbstractValue rewrittenWithLens(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java
new file mode 100644
index 0000000..836ccd5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import static com.android.tools.r8.ir.analysis.value.AbstractValue.unknown;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+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.horizontalclassmerging.HorizontalClassMergerUtils;
+
+public abstract class AbstractValueJoiner {
+
+  protected final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+  private AbstractValueJoiner(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.appView = appView;
+  }
+
+  final AbstractValue internalJoin(
+      AbstractValue abstractValue,
+      AbstractValue otherAbstractValue,
+      AbstractValueJoinerConfig config,
+      DexType type) {
+    if (abstractValue.isBottom() || otherAbstractValue.isUnknown()) {
+      return otherAbstractValue;
+    }
+    if (abstractValue.isUnknown()
+        || otherAbstractValue.isBottom()
+        || abstractValue.equals(otherAbstractValue)) {
+      return abstractValue;
+    }
+    return type.isReferenceType()
+        ? joinReference(abstractValue, otherAbstractValue)
+        : joinPrimitive(abstractValue, otherAbstractValue, config);
+  }
+
+  private AbstractValue joinPrimitive(
+      AbstractValue abstractValue,
+      AbstractValue otherAbstractValue,
+      AbstractValueJoinerConfig config) {
+    assert !abstractValue.isNullOrAbstractValue();
+    assert !otherAbstractValue.isNullOrAbstractValue();
+
+    if (config.canUseDefiniteBitsAbstraction()
+        && abstractValue.isConstantOrNonConstantNumberValue()
+        && otherAbstractValue.isConstantOrNonConstantNumberValue()) {
+      // TODO(b/196017578): Implement join.
+    }
+
+    if (config.canUseNumberIntervalAndNumberSetAbstraction()
+        && abstractValue.isConstantOrNonConstantNumberValue()
+        && otherAbstractValue.isConstantOrNonConstantNumberValue()) {
+      NumberFromSetValue.Builder numberFromSetValueBuilder;
+      if (abstractValue.isSingleNumberValue()) {
+        numberFromSetValueBuilder = NumberFromSetValue.builder(abstractValue.asSingleNumberValue());
+      } else {
+        assert abstractValue.isNumberFromSetValue();
+        numberFromSetValueBuilder = abstractValue.asNumberFromSetValue().instanceBuilder();
+      }
+      if (otherAbstractValue.isSingleNumberValue()) {
+        numberFromSetValueBuilder.addInt(otherAbstractValue.asSingleNumberValue().getIntValue());
+      } else {
+        assert otherAbstractValue.isNumberFromSetValue();
+        numberFromSetValueBuilder.addInts(otherAbstractValue.asNumberFromSetValue());
+      }
+      return numberFromSetValueBuilder.build(appView.abstractValueFactory());
+    }
+
+    return unknown();
+  }
+
+  private AbstractValue joinReference(
+      AbstractValue abstractValue, AbstractValue otherAbstractValue) {
+    if (abstractValue.isNull()) {
+      return NullOrAbstractValue.create(otherAbstractValue);
+    }
+    if (otherAbstractValue.isNull()) {
+      return NullOrAbstractValue.create(abstractValue);
+    }
+    if (abstractValue.isNullOrAbstractValue()
+        && abstractValue.asNullOrAbstractValue().getNonNullValue().equals(otherAbstractValue)) {
+      return abstractValue;
+    }
+    if (otherAbstractValue.isNullOrAbstractValue()
+        && otherAbstractValue.asNullOrAbstractValue().getNonNullValue().equals(abstractValue)) {
+      return otherAbstractValue;
+    }
+    return unknown();
+  }
+
+  public static class AbstractValueFieldJoiner extends AbstractValueJoiner {
+
+    public AbstractValueFieldJoiner(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      super(appView);
+    }
+
+    public AbstractValue join(
+        AbstractValue abstractValue, AbstractValue otherAbstractValue, ProgramField field) {
+      AbstractValueJoinerConfig config = getConfig(field);
+      AbstractValue result =
+          internalJoin(abstractValue, otherAbstractValue, config, field.getType());
+      assert result.equals(
+          internalJoin(otherAbstractValue, abstractValue, config, field.getType()));
+      return result;
+    }
+
+    private AbstractValueJoinerConfig getConfig(ProgramField field) {
+      if (HorizontalClassMergerUtils.isClassIdField(appView, field)) {
+        return AbstractValueJoinerConfig.getClassIdFieldConfig();
+      }
+      return AbstractValueJoinerConfig.getDefaultConfig();
+    }
+  }
+
+  public static class AbstractValueParameterJoiner extends AbstractValueJoiner {
+
+    public AbstractValueParameterJoiner(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      super(appView);
+    }
+
+    public AbstractValue join(
+        AbstractValue abstractValue, AbstractValue otherAbstractValue, DexType type) {
+      // TODO(b/196017578): Use a config that allows the definite bits abstraction for parameters
+      //  used in bitwise operations.
+      AbstractValueJoinerConfig config = AbstractValueJoinerConfig.getDefaultConfig();
+      AbstractValue result = internalJoin(abstractValue, otherAbstractValue, config, type);
+      assert result.equals(internalJoin(otherAbstractValue, abstractValue, config, type));
+      return result;
+    }
+  }
+
+  private static class AbstractValueJoinerConfig {
+
+    // The power set lattice is an expensive abstraction, so use it with caution.
+    private static final AbstractValueJoinerConfig CLASS_ID_FIELD_CONFIG =
+        new AbstractValueJoinerConfig().setCanUseNumberIntervalAndNumberSetAbstraction();
+
+    private static final AbstractValueJoinerConfig DEFAULT_CONFIG = new AbstractValueJoinerConfig();
+
+    public static AbstractValueJoinerConfig getClassIdFieldConfig() {
+      return CLASS_ID_FIELD_CONFIG;
+    }
+
+    public static AbstractValueJoinerConfig getDefaultConfig() {
+      return DEFAULT_CONFIG;
+    }
+
+    private boolean canUseDefiniteBitsAbstraction;
+    private boolean canUseNumberIntervalAndNumberSetAbstraction;
+
+    boolean canUseDefiniteBitsAbstraction() {
+      return canUseDefiniteBitsAbstraction;
+    }
+
+    @SuppressWarnings("UnusedMethod")
+    AbstractValueJoinerConfig setCanUseDefiniteBitsAbstraction() {
+      canUseDefiniteBitsAbstraction = true;
+      return this;
+    }
+
+    boolean canUseNumberIntervalAndNumberSetAbstraction() {
+      return canUseNumberIntervalAndNumberSetAbstraction;
+    }
+
+    AbstractValueJoinerConfig setCanUseNumberIntervalAndNumberSetAbstraction() {
+      canUseNumberIntervalAndNumberSetAbstraction = true;
+      return this;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
new file mode 100644
index 0000000..b713cef
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.Objects;
+
+public class DefiniteBitsNumberValue extends NonConstantNumberValue {
+
+  private final int definitelySetBits;
+  private final int definitelyUnsetBits;
+
+  public DefiniteBitsNumberValue(int definitelySetBits, int definitelyUnsetBits) {
+    assert (definitelySetBits & definitelyUnsetBits) == 0;
+    this.definitelySetBits = definitelySetBits;
+    this.definitelyUnsetBits = definitelyUnsetBits;
+  }
+
+  @Override
+  public boolean containsInt(int value) {
+    return false;
+  }
+
+  @Override
+  public long getAbstractionSize() {
+    return Long.MAX_VALUE;
+  }
+
+  @Override
+  public boolean isDefiniteBitsNumberValue() {
+    return true;
+  }
+
+  @Override
+  public DefiniteBitsNumberValue asDefiniteBitsNumberValue() {
+    return this;
+  }
+
+  @Override
+  public boolean isNonTrivial() {
+    return true;
+  }
+
+  @Override
+  public OptionalBool isSubsetOf(int[] values) {
+    return OptionalBool.unknown();
+  }
+
+  @Override
+  public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) {
+    return true;
+  }
+
+  @Override
+  public AbstractValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || o.getClass() != getClass()) {
+      return false;
+    }
+    DefiniteBitsNumberValue definiteBitsNumberValue = (DefiniteBitsNumberValue) o;
+    return definitelySetBits == definiteBitsNumberValue.definitelySetBits
+        && definitelyUnsetBits == definiteBitsNumberValue.definitelyUnsetBits;
+  }
+
+  @Override
+  public int hashCode() {
+    int hash = 31 * (31 * (31 + definitelySetBits) + definitelyUnsetBits);
+    assert hash == Objects.hash(definitelySetBits, definitelyUnsetBits);
+    return hash;
+  }
+
+  @Override
+  public String toString() {
+    return "DefiniteBitsNumberValue(set: "
+        + Integer.toBinaryString(definitelySetBits)
+        + "; unset: "
+        + Integer.toBinaryString(definitelyUnsetBits)
+        + ")";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
index fab9f6f..43da39b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
@@ -105,15 +105,11 @@
       DexType parameterType,
       Action onChangedAction) {
     assert parameterType.isClassType();
-    boolean allowNullOrAbstractValue = true;
-    boolean allowNonConstantNumbers = false;
     AbstractValue oldAbstractValue = abstractValue;
     abstractValue =
-        abstractValue.join(
-            parameterState.getAbstractValue(appView),
-            appView.abstractValueFactory(),
-            allowNullOrAbstractValue,
-            allowNonConstantNumbers);
+        appView
+            .getAbstractValueParameterJoiner()
+            .join(abstractValue, parameterState.getAbstractValue(appView), parameterType);
     DynamicType oldDynamicType = dynamicType;
     DynamicType joinedDynamicType = dynamicType.join(appView, parameterState.getDynamicType());
     DynamicType widenedDynamicType =
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
index 429e514..3b3f3cb 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
@@ -56,15 +56,11 @@
       DexType parameterType,
       Action onChangedAction) {
     assert parameterType.isPrimitiveType();
-    boolean allowNullOrAbstractValue = false;
-    boolean allowNonConstantNumbers = false;
     AbstractValue oldAbstractValue = abstractValue;
     abstractValue =
-        abstractValue.join(
-            parameterState.abstractValue,
-            appView.abstractValueFactory(),
-            allowNullOrAbstractValue,
-            allowNonConstantNumbers);
+        appView
+            .getAbstractValueParameterJoiner()
+            .join(abstractValue, parameterState.abstractValue, parameterType);
     if (abstractValue.isUnknown()) {
       return unknown();
     }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
index 2a044e5..bbc27a2 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
@@ -5,8 +5,10 @@
 package com.android.tools.r8.utils.collections;
 
 import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.utils.TriPredicate;
 import com.google.common.base.Equivalence.Wrapper;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
@@ -77,6 +79,12 @@
         .removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue()));
   }
 
+  public boolean removeIf(TriPredicate<K, V, Entry<?, V>> predicate) {
+    return backing
+        .entrySet()
+        .removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue(), entry));
+  }
+
   public int size() {
     return backing.size();
   }