Fix nondeterminism in argument propagation

Bug: b/364774724
Change-Id: Ic5e607d9bfc6835b8298ca261cd752bef2e52d2c
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index d25c1b5..7e02b35 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -255,7 +255,7 @@
     if (fieldAccessAnalysis != null && fieldAccessAnalysis.fieldAssignmentTracker() != null) {
       fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
     }
-    appView.withArgumentPropagator(ArgumentPropagator::publishDelayedReprocessingCriteria);
+    appView.withArgumentPropagator(ArgumentPropagator::waveDone);
     if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
       appView.protoShrinker().protoEnumSwitchMapRemover.updateVisibleStaticFieldValues();
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 30b9398..063b69b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -170,6 +170,13 @@
     reprocessingCriteriaCollection.publishDelayedReprocessingCriteria();
   }
 
+  public void waveDone() {
+    if (codeScanner != null) {
+      codeScanner.waveDone();
+    }
+    publishDelayedReprocessingCriteria();
+  }
+
   public void transferArgumentInformation(ProgramMethod from, ProgramMethod to) {
     assert codeScanner != null;
     MethodStateCollectionByReference methodStates = codeScanner.getMethodStates();
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 2f6dff1..c4534d1 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
@@ -83,6 +83,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.TraversalUtils;
 import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.ProgramFieldSet;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.google.common.collect.Sets;
 import java.io.IOException;
@@ -134,6 +135,9 @@
    */
   private final FieldStateCollection fieldStates = FieldStateCollection.createConcurrent();
 
+  private final ProgramFieldSet newlyUnknownFieldsInCurrentWave =
+      ProgramFieldSet.createConcurrent();
+
   /**
    * The abstract program state for this optimization. Intuitively maps each parameter to its
    * abstract value and dynamic type.
@@ -204,7 +208,9 @@
     // 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);
+    return fieldStates.get(field).isUnknown()
+        && field.getType().isIdenticalTo(staticType)
+        && !newlyUnknownFieldsInCurrentWave.contains(field);
   }
 
   protected boolean isMethodParameterAlreadyUnknown(
@@ -254,6 +260,10 @@
         .scan(timing);
   }
 
+  public void waveDone() {
+    newlyUnknownFieldsInCurrentWave.clear();
+  }
+
   protected class CodeScanner {
 
     protected final AbstractValueSupplier abstractValueSupplier;
@@ -315,7 +325,8 @@
                     StateCloner.getCloner(),
                     Action.empty());
             return narrowFieldState(field, newFieldState);
-          });
+          },
+          () -> newlyUnknownFieldsInCurrentWave.add(field));
       timing.end();
     }
 
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 cf0dcc5..11c52ce 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
@@ -56,12 +56,25 @@
       Supplier<NonEmptyValueState> fieldStateSupplier,
       Timing timing,
       BiFunction<ConcreteValueState, ConcreteValueState, NonEmptyValueState> joiner) {
+    return addTemporaryFieldState(field, fieldStateSupplier, timing, joiner, Action.empty());
+  }
+
+  public NonEmptyValueState addTemporaryFieldState(
+      ProgramField field,
+      Supplier<NonEmptyValueState> fieldStateSupplier,
+      Timing timing,
+      BiFunction<ConcreteValueState, ConcreteValueState, NonEmptyValueState> joiner,
+      Action unknownTransitionHandler) {
     ValueState joinState =
         fieldStates.compute(
             field,
             (f, existingFieldState) -> {
               if (existingFieldState == null) {
-                return fieldStateSupplier.get();
+                NonEmptyValueState result = fieldStateSupplier.get();
+                if (result.isUnknown()) {
+                  unknownTransitionHandler.execute();
+                }
+                return result;
               }
               assert !existingFieldState.isBottom();
               if (existingFieldState.isUnknown()) {
@@ -69,6 +82,7 @@
               }
               NonEmptyValueState fieldStateToAdd = fieldStateSupplier.get();
               if (fieldStateToAdd.isUnknown()) {
+                unknownTransitionHandler.execute();
                 return fieldStateToAdd;
               }
               timing.begin("Join temporary field state");
@@ -76,6 +90,9 @@
               ConcreteValueState concreteFieldStateToAdd = fieldStateToAdd.asConcrete();
               NonEmptyValueState joinResult =
                   joiner.apply(existingConcreteFieldState, concreteFieldStateToAdd);
+              if (joinResult.isUnknown()) {
+                unknownTransitionHandler.execute();
+              }
               timing.end();
               return joinResult;
             });