Deterministic ordering in presence of if-then-else functions

Change-Id: I4ae52c9842a5273ff0bcf8a6263e4a25e6cd691c
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 1d0edbe..a17aa98 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysis;
@@ -249,6 +250,7 @@
     assert appView.isAllCodeProcessed();
     FieldStateCollection fieldStates = codeScanner.getFieldStates();
     MethodStateCollectionByReference methodStates = codeScanner.getMethodStates();
+    InFlowComparator inFlowComparator = codeScanner.getInFlowComparator();
     appView.testing().argumentPropagatorEventConsumer.acceptCodeScannerResult(methodStates);
     codeScanner = null;
 
@@ -261,6 +263,7 @@
             immediateSubtypingInfo,
             fieldStates,
             methodStates,
+            inFlowComparator,
             stronglyConnectedProgramComponents,
             interfaceDispatchOutsideProgram)
         .propagateOptimizationInfo(
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 b29eff6..a3961f0 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
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.optimize.argumentpropagation;
 
-
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -39,6 +38,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
@@ -58,6 +58,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.IfThenElseAbstractFunction;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.InstanceFieldReadAbstractFunction;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
@@ -80,6 +81,8 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -134,6 +137,8 @@
   private final MethodStateCollectionByReference methodStates =
       MethodStateCollectionByReference.createConcurrent();
 
+  private final InFlowComparator.Builder inFlowComparatorBuilder = InFlowComparator.builder();
+
   public ArgumentPropagatorCodeScanner(AppView<AppInfoWithLiveness> appView) {
     this(appView, new ArgumentPropagatorReprocessingCriteriaCollection(appView));
   }
@@ -166,6 +171,10 @@
     return virtualRootMethods.get(method.getReference());
   }
 
+  InFlowComparator getInFlowComparator() {
+    return inFlowComparatorBuilder.build();
+  }
+
   // TODO(b/296030319): Allow lookups in the FieldStateCollection using DexField keys to avoid the
   //  need for definitionFor here.
   private boolean isFieldValueAlreadyUnknown(DexType staticType, DexField field) {
@@ -231,6 +240,7 @@
 
     private SuccessfulDataflowAnalysisResult<BasicBlock, PathConstraintAnalysisState>
         pathConstraintAnalysisResult;
+    private Object2IntMap<Phi> phiNumbering = null;
 
     protected CodeScanner(
         AbstractValueSupplier abstractValueSupplier, IRCode code, ProgramMethod method) {
@@ -359,6 +369,9 @@
     // 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.
+    // TODO(b/302281503): Cache computed in flow so that we do not compute the same in flow for the
+    //  same value multiple times.
+    // TODO(b/302281503): Canonicalize computed in flow.
     private InFlow computeInFlow(
         DexType staticType,
         Value value,
@@ -420,10 +433,34 @@
       }
       NonEmptyValueState leftValue = valueStateSupplier.apply(phi.getOperand(0));
       NonEmptyValueState rightValue = valueStateSupplier.apply(phi.getOperand(1));
-      if (leftPredecessorPathConstraint.isNegated(condition)) {
-        return new IfThenElseAbstractFunction(condition, rightValue, leftValue);
+      IfThenElseAbstractFunction result =
+          leftPredecessorPathConstraint.isNegated(condition)
+              ? new IfThenElseAbstractFunction(condition, rightValue, leftValue)
+              : new IfThenElseAbstractFunction(condition, leftValue, rightValue);
+      recordIfThenElsePosition(result, phi);
+      return result;
+    }
+
+    private void recordIfThenElsePosition(
+        IfThenElseAbstractFunction ifThenElseAbstractFunction, Phi phi) {
+      inFlowComparatorBuilder.addIfThenElsePosition(
+          ifThenElseAbstractFunction,
+          SourcePosition.builder()
+              .setMethod(code.context().getReference())
+              .setLine(getOrCreatePhiNumbering().getInt(phi))
+              .build());
+    }
+
+    private Object2IntMap<Phi> getOrCreatePhiNumbering() {
+      if (phiNumbering == null) {
+        phiNumbering = new Object2IntOpenHashMap<>();
+        for (BasicBlock block : code.getBlocks()) {
+          for (Phi phi : block.getPhis()) {
+            phiNumbering.put(phi, phiNumbering.size());
+          }
+        }
       }
-      return new IfThenElseAbstractFunction(condition, leftValue, rightValue);
+      return phiNumbering;
     }
 
     private InFlow castBaseInFlow(InFlow inFlow, Value value) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
index 99b3640..800b277 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.propagation.InFlowPropagator;
 import com.android.tools.r8.optimize.argumentpropagation.propagation.InterfaceMethodArgumentPropagator;
@@ -34,6 +35,7 @@
   private final PrimaryR8IRConverter converter;
   private final FieldStateCollection fieldStates;
   private final MethodStateCollectionByReference methodStates;
+  private final InFlowComparator inFlowComparator;
 
   private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
   private final List<Set<DexProgramClass>> stronglyConnectedProgramComponents;
@@ -47,6 +49,7 @@
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
       FieldStateCollection fieldStates,
       MethodStateCollectionByReference methodStates,
+      InFlowComparator inFlowComparator,
       List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
       BiConsumer<Set<DexProgramClass>, DexMethodSignature> interfaceDispatchOutsideProgram) {
     this.appView = appView;
@@ -54,6 +57,7 @@
     this.immediateSubtypingInfo = immediateSubtypingInfo;
     this.fieldStates = fieldStates;
     this.methodStates = methodStates;
+    this.inFlowComparator = inFlowComparator;
     this.stronglyConnectedProgramComponents = stronglyConnectedProgramComponents;
     this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram;
   }
@@ -79,7 +83,8 @@
             classesWithSingleCallerInlinedInstanceInitializers,
             converter,
             fieldStates,
-            methodStates)
+            methodStates,
+            inFlowComparator)
         .run(executorService);
     timing.end();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomValueState.java
index 609b769..a267042 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomValueState.java
@@ -31,4 +31,14 @@
   public ValueState mutableCopyWithoutInFlow() {
     return this;
   }
+
+  @Override
+  public final boolean equals(Object obj) {
+    return this == obj;
+  }
+
+  @Override
+  public final int hashCode() {
+    return System.identityHashCode(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/CastAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/CastAbstractFunction.java
index a4eb77e..2586f8d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/CastAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/CastAbstractFunction.java
@@ -44,10 +44,12 @@
   }
 
   @Override
-  public int internalCompareToSameKind(InFlow other) {
+  public int internalCompareToSameKind(InFlow other, InFlowComparator comparator) {
     CastAbstractFunction fn = other.asCastAbstractFunction();
     if (inFlow != fn.inFlow) {
-      return inFlow.compareTo(fn.inFlow);
+      int result = inFlow.compareTo(fn.inFlow, comparator);
+      assert result != 0;
+      return result;
     }
     return type.compareTo(fn.type);
   }
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 26dc74a..ca98915 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
@@ -14,6 +14,7 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.SetUtils;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Supplier;
 
@@ -136,6 +137,23 @@
   }
 
   @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof ConcreteArrayTypeValueState)) {
+      return false;
+    }
+    ConcreteArrayTypeValueState state = (ConcreteArrayTypeValueState) obj;
+    return nullability.equals(state.nullability) && getInFlow().equals(state.getInFlow());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getClass(), nullability, getInFlow());
+  }
+
+  @Override
   public String toString() {
     assert !hasInFlow();
     return "ArrayState(" + nullability + ")";
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 f6a608d..96e476b 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
@@ -15,6 +15,7 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.SetUtils;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Supplier;
 
@@ -189,6 +190,25 @@
   }
 
   @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof ConcreteClassTypeValueState)) {
+      return false;
+    }
+    ConcreteClassTypeValueState state = (ConcreteClassTypeValueState) obj;
+    return abstractValue.equals(state.abstractValue)
+        && dynamicType.equals(state.dynamicType)
+        && getInFlow().equals(state.getInFlow());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getClass(), abstractValue, dynamicType, getInFlow());
+  }
+
+  @Override
   public String toString() {
     assert !hasInFlow();
     return "ClassState(type: " + dynamicType + ", value: " + abstractValue + ")";
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
index 864c16e..118c05d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.SetUtils;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Supplier;
 
@@ -129,6 +130,23 @@
   }
 
   @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof ConcretePrimitiveTypeValueState)) {
+      return false;
+    }
+    ConcretePrimitiveTypeValueState state = (ConcretePrimitiveTypeValueState) obj;
+    return abstractValue.equals(state.abstractValue) && getInFlow().equals(state.getInFlow());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getClass(), abstractValue, getInFlow());
+  }
+
+  @Override
   public String toString() {
     assert !hasInFlow();
     return "PrimitiveState(" + abstractValue + ")";
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 350d7f9..04711fe 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Supplier;
 
@@ -147,6 +148,23 @@
   }
 
   @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof ConcreteReceiverValueState)) {
+      return false;
+    }
+    ConcreteReceiverValueState state = (ConcreteReceiverValueState) obj;
+    return dynamicType.equals(state.dynamicType) && getInFlow().equals(state.getInFlow());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getClass(), dynamicType, getInFlow());
+  }
+
+  @Override
   public String toString() {
     assert !hasInFlow();
     return "ReceiverState(" + dynamicType + ")";
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java
index 0b9e152..99fcf7b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java
@@ -25,7 +25,7 @@
   }
 
   @Override
-  public int internalCompareToSameKind(InFlow other) {
+  public int internalCompareToSameKind(InFlow other, InFlowComparator comparator) {
     return field.compareTo(other.asFieldValue().getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java
index 1b33495b..f31b821 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java
@@ -41,7 +41,7 @@
   }
 
   @Override
-  public int internalCompareToSameKind(InFlow inFlow) {
+  public int internalCompareToSameKind(InFlow inFlow, InFlowComparator comparator) {
     assert this == inFlow;
     return 0;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java
index a6da375..4d21db9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java
@@ -6,10 +6,12 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Represents a ternary expression (exp ? u : v). The {@link #condition} is an expression containing
@@ -133,9 +135,11 @@
   }
 
   @Override
-  public int internalCompareToSameKind(InFlow inFlow) {
-    // TODO(b/302281503): Find a way to make this comparable.
-    return hashCode() - inFlow.hashCode();
+  public int internalCompareToSameKind(InFlow inFlow, InFlowComparator comparator) {
+    SourcePosition position = comparator.getIfThenElsePosition(this);
+    SourcePosition otherPosition =
+        comparator.getIfThenElsePosition(inFlow.asIfThenElseAbstractFunction());
+    return position.compareTo(otherPosition);
   }
 
   @Override
@@ -152,4 +156,23 @@
   public InFlowKind getKind() {
     return InFlowKind.ABSTRACT_FUNCTION_IF_THEN_ELSE;
   }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof IfThenElseAbstractFunction)) {
+      return false;
+    }
+    IfThenElseAbstractFunction fn = (IfThenElseAbstractFunction) obj;
+    return condition.equals(fn.condition)
+        && thenState.equals(fn.thenState)
+        && elseState.equals(fn.elseState);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getClass(), condition, thenState, elseState);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
index 4590558..3c28c20 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
@@ -7,17 +7,16 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.optimize.compose.UpdateChangedFlagsAbstractFunction;
 
-public interface InFlow extends Comparable<InFlow> {
+public interface InFlow {
 
-  @Override
-  default int compareTo(InFlow inFlow) {
+  default int compareTo(InFlow inFlow, InFlowComparator comparator) {
     if (getKind() == inFlow.getKind()) {
-      return internalCompareToSameKind(inFlow);
+      return internalCompareToSameKind(inFlow, comparator);
     }
     return getKind().ordinal() - inFlow.getKind().ordinal();
   }
 
-  int internalCompareToSameKind(InFlow inFlow);
+  int internalCompareToSameKind(InFlow inFlow, InFlowComparator comparator);
 
   InFlowKind getKind();
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowComparator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowComparator.java
new file mode 100644
index 0000000..82a1cc8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowComparator.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+public class InFlowComparator implements Comparator<InFlow> {
+
+  private final Map<IfThenElseAbstractFunction, SourcePosition> ifThenElsePositions;
+
+  private InFlowComparator(Map<IfThenElseAbstractFunction, SourcePosition> ifThenElsePositions) {
+    this.ifThenElsePositions = ifThenElsePositions;
+  }
+
+  public SourcePosition getIfThenElsePosition(IfThenElseAbstractFunction fn) {
+    SourcePosition position = ifThenElsePositions.get(fn);
+    assert position != null;
+    return position;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public void clear() {
+    ifThenElsePositions.clear();
+  }
+
+  @Override
+  public int compare(InFlow inFlow, InFlow other) {
+    return inFlow.compareTo(other, this);
+  }
+
+  public static class Builder {
+
+    private final Map<IfThenElseAbstractFunction, SourcePosition> ifThenElsePositions =
+        new HashMap<>();
+
+    public void addIfThenElsePosition(IfThenElseAbstractFunction fn, SourcePosition position) {
+      synchronized (ifThenElsePositions) {
+        ifThenElsePositions.put(fn, position);
+      }
+    }
+
+    public InFlowComparator build() {
+      return new InFlowComparator(ifThenElsePositions);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
index d9c23c4..382706b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
@@ -74,9 +74,9 @@
   }
 
   @Override
-  public int internalCompareToSameKind(InFlow other) {
+  public int internalCompareToSameKind(InFlow other, InFlowComparator comparator) {
     InstanceFieldReadAbstractFunction fn = other.asInstanceFieldReadAbstractFunction();
-    int result = receiver.compareTo(fn.receiver);
+    int result = receiver.compareTo(fn.receiver, comparator);
     if (result == 0) {
       result = field.compareTo(fn.field);
     }
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 138725e..ed49cea 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
@@ -63,7 +63,7 @@
   }
 
   @Override
-  public int internalCompareToSameKind(InFlow other) {
+  public int internalCompareToSameKind(InFlow other, InFlowComparator comparator) {
     MethodParameter methodParameter = other.asMethodParameter();
     int result = method.compareTo(methodParameter.method);
     if (result == 0) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java
index 8e84a76..c8294a4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java
@@ -67,9 +67,9 @@
   }
 
   @Override
-  public int internalCompareToSameKind(InFlow other) {
+  public int internalCompareToSameKind(InFlow other, InFlowComparator comparator) {
     OrAbstractFunction fn = other.asOrAbstractFunction();
-    int result = inFlow.compareTo(fn.inFlow);
+    int result = inFlow.compareTo(fn.inFlow, comparator);
     if (result == 0) {
       result = constant.getIntValue() - fn.constant.getIntValue();
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java
index dd06a31..726ade5 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java
@@ -41,7 +41,7 @@
   }
 
   @Override
-  public int internalCompareToSameKind(InFlow inFlow) {
+  public int internalCompareToSameKind(InFlow inFlow, InFlowComparator comparator) {
     assert this == inFlow;
     return 0;
   }
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 6fdedc4..69c1fc8 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
@@ -53,6 +53,16 @@
   }
 
   @Override
+  public boolean equals(Object obj) {
+    return this == obj;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
   public String toString() {
     return "⊤";
   }
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 b88d240..313b340 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
@@ -136,4 +136,10 @@
       DexType outStaticType,
       StateCloner cloner,
       Action onChangedAction);
+
+  @Override
+  public abstract boolean equals(Object obj);
+
+  @Override
+  public abstract int hashCode();
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java
index c9649f8..98247c0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
@@ -58,8 +59,9 @@
       AppView<AppInfoWithLiveness> appView,
       IRConverter converter,
       FieldStateCollection fieldStates,
-      MethodStateCollectionByReference methodStates) {
-    return new FlowGraphBuilder(appView, converter, fieldStates, methodStates);
+      MethodStateCollectionByReference methodStates,
+      InFlowComparator inFlowComparator) {
+    return new FlowGraphBuilder(appView, converter, fieldStates, methodStates, inFlowComparator);
   }
 
   @Override
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 d564575..4639273 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
@@ -22,6 +22,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValue;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
@@ -32,7 +33,6 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
-import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
 
@@ -42,6 +42,7 @@
   private final IRConverter converter;
   private final FieldStateCollection fieldStates;
   private final MethodStateCollectionByReference methodStates;
+  private final InFlowComparator inFlowComparator;
 
   private final LinkedHashMap<DexField, FlowGraphFieldNode> fieldNodes = new LinkedHashMap<>();
   private final LinkedHashMap<DexMethod, Int2ReferenceMap<FlowGraphParameterNode>> parameterNodes =
@@ -51,15 +52,18 @@
       AppView<AppInfoWithLiveness> appView,
       IRConverter converter,
       FieldStateCollection fieldStates,
-      MethodStateCollectionByReference methodStates) {
+      MethodStateCollectionByReference methodStates,
+      InFlowComparator inFlowComparator) {
     this.appView = appView;
     this.converter = converter;
     this.fieldStates = fieldStates;
     this.methodStates = methodStates;
+    this.inFlowComparator = inFlowComparator;
   }
 
   public FlowGraphBuilder addClasses() {
     appView.appInfo().classesWithDeterministicOrder().forEach(this::add);
+    inFlowComparator.clear();
     return this;
   }
 
@@ -96,7 +100,7 @@
 
     FlowGraphFieldNode node = getOrCreateFieldNode(field, concreteFieldState);
     List<InFlow> inFlowWithDeterministicOrder =
-        ListUtils.sort(concreteFieldState.getInFlow(), Comparator.naturalOrder());
+        ListUtils.sort(concreteFieldState.getInFlow(), inFlowComparator);
     for (InFlow inFlow : inFlowWithDeterministicOrder) {
       if (addInFlow(inFlow, node).shouldBreak()) {
         assert node.isUnknown();
@@ -143,7 +147,9 @@
     }
 
     FlowGraphParameterNode node = getOrCreateParameterNode(method, parameterIndex, methodState);
-    for (InFlow inFlow : concreteParameterState.getInFlow()) {
+    List<InFlow> inFlowWithDeterministicOrder =
+        ListUtils.sort(concreteParameterState.getInFlow(), inFlowComparator);
+    for (InFlow inFlow : inFlowWithDeterministicOrder) {
       if (addInFlow(inFlow, node).shouldBreak()) {
         assert node.isUnknown();
         break;
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 81e3424..a2ee3c9 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
@@ -24,6 +24,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
@@ -48,19 +49,22 @@
   final IRConverter converter;
   protected final FieldStateCollection fieldStates;
   final MethodStateCollectionByReference methodStates;
+  final InFlowComparator inFlowComparator;
 
   public InFlowPropagator(
       AppView<AppInfoWithLiveness> appView,
       Set<DexProgramClass> classesWithSingleCallerInlinedInstanceInitializers,
       IRConverter converter,
       FieldStateCollection fieldStates,
-      MethodStateCollectionByReference methodStates) {
+      MethodStateCollectionByReference methodStates,
+      InFlowComparator inFlowComparator) {
     this.appView = appView;
     this.classesWithSingleCallerInlinedInstanceInitializers =
         classesWithSingleCallerInlinedInstanceInitializers;
     this.converter = converter;
     this.fieldStates = fieldStates;
     this.methodStates = methodStates;
+    this.inFlowComparator = inFlowComparator;
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
@@ -97,7 +101,7 @@
     // Build a graph with an edge from parameter p -> parameter p' if all argument information for p
     // must be included in the argument information for p'.
     FlowGraph flowGraph =
-        FlowGraph.builder(appView, converter, fieldStates, methodStates)
+        FlowGraph.builder(appView, converter, fieldStates, methodStates, inFlowComparator)
             .addClasses()
             .clearInFlow()
             .build();
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 22ed377..210a674 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
@@ -30,6 +30,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
@@ -95,9 +96,15 @@
       throws ExecutionException {
     prepareForInFlowPropagator();
 
+    InFlowComparator emptyComparator = InFlowComparator.builder().build();
     InFlowPropagator inFlowPropagator =
         new InFlowPropagator(
-            appView, null, converter, codeScanner.getFieldStates(), codeScanner.getMethodStates()) {
+            appView,
+            null,
+            converter,
+            codeScanner.getFieldStates(),
+            codeScanner.getMethodStates(),
+            emptyComparator) {
 
           @Override
           protected DefaultFieldValueJoiner createDefaultFieldValueJoiner(
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
index e57b4fb..91476d3 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowKind;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.OrAbstractFunction;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
@@ -131,8 +132,8 @@
   }
 
   @Override
-  public int internalCompareToSameKind(InFlow other) {
-    return inFlow.compareTo(other.asUpdateChangedFlagsAbstractFunction().inFlow);
+  public int internalCompareToSameKind(InFlow other, InFlowComparator comparator) {
+    return inFlow.compareTo(other.asUpdateChangedFlagsAbstractFunction().inFlow, comparator);
   }
 
   @Override