Simplify Compose bit patterns

Bug: b/332668257
Change-Id: Id7257a0f1d84b13d4474b3e39f13b1e179a6db43
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopDescriptor.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopDescriptor.java
new file mode 100644
index 0000000..2862449
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopDescriptor.java
@@ -0,0 +1,328 @@
+// 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.ir.conversion.passes;
+
+import static com.android.tools.r8.utils.BitUtils.ALL_BITS_SET_MASK;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.Add;
+import com.android.tools.r8.ir.code.And;
+import com.android.tools.r8.ir.code.Binop;
+import com.android.tools.r8.ir.code.Mul;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Or;
+import com.android.tools.r8.ir.code.Shl;
+import com.android.tools.r8.ir.code.Shr;
+import com.android.tools.r8.ir.code.Sub;
+import com.android.tools.r8.ir.code.Ushr;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.Xor;
+
+/**
+ * A Binop descriptor describes left and right identity and absorbing element of binop. <code>
+ *  In a space K, for a binop *:
+ * - i is left identity if for each x in K, i * x = x.
+ * - i is right identity if for each x in K, x * i = x.
+ * - a is left absorbing if for each x in K, a * x = a.
+ * - a is right absorbing if for each x in K, x * a = a.
+ * In a space K, a binop * is associative if for each x,y,z in K, (x * y) * z = x * (y * z).
+ * </code>
+ */
+enum BinopDescriptor {
+  ADD(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return Add.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left + right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left + right;
+    }
+  },
+  SUB(false) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return new Sub(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left - right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left - right;
+    }
+  },
+  MUL(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return Mul.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return 1;
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 1;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left * right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left * right;
+    }
+  },
+  // The following two can be improved if we handle ZeroDivide.
+  DIV(false) {
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 1;
+    }
+  },
+  REM(false),
+  AND(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return And.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return allBitsSet(isBooleanValue);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return allBitsSet(isBooleanValue);
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left & right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left & right;
+    }
+  },
+  OR(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return Or.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return allBitsSet(isBooleanValue);
+    }
+
+    @Override
+    Integer rightAbsorbing(boolean isBooleanValue) {
+      return allBitsSet(isBooleanValue);
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left | right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left | right;
+    }
+  },
+  XOR(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return Xor.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left ^ right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left ^ right;
+    }
+  },
+  SHL(false) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return new Shl(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    boolean isShift() {
+      return true;
+    }
+  },
+  SHR(false) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return new Shr(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    boolean isShift() {
+      return true;
+    }
+  },
+  USHR(false) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return new Ushr(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    boolean isShift() {
+      return true;
+    }
+  };
+
+  final boolean associativeAndCommutative;
+
+  BinopDescriptor(boolean associativeAndCommutative) {
+    this.associativeAndCommutative = associativeAndCommutative;
+  }
+
+  Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+    throw new Unreachable();
+  }
+
+  Integer allBitsSet(boolean isBooleanValue) {
+    return isBooleanValue ? 1 : ALL_BITS_SET_MASK;
+  }
+
+  Integer leftIdentity(boolean isBooleanValue) {
+    return null;
+  }
+
+  Integer rightIdentity(boolean isBooleanValue) {
+    return null;
+  }
+
+  Integer leftAbsorbing(boolean isBooleanValue) {
+    return null;
+  }
+
+  Integer rightAbsorbing(boolean isBooleanValue) {
+    return null;
+  }
+
+  int evaluate(int left, int right) {
+    throw new Unreachable();
+  }
+
+  long evaluate(long left, long right) {
+    throw new Unreachable();
+  }
+
+  boolean isShift() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
index 1f731e4..7a480d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.ir.conversion.passes;
 
-import static com.android.tools.r8.utils.BitUtils.ALL_BITS_SET_MASK;
+import static com.android.tools.r8.ir.conversion.passes.BinopDescriptor.ADD;
+import static com.android.tools.r8.ir.conversion.passes.BinopDescriptor.SUB;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -18,10 +18,12 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.LogicalBinop;
 import com.android.tools.r8.ir.code.Mul;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Or;
 import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Shl;
 import com.android.tools.r8.ir.code.Shr;
@@ -45,8 +47,8 @@
 
   private Map<Class<?>, BinopDescriptor> createBinopDescriptors() {
     ImmutableMap.Builder<Class<?>, BinopDescriptor> builder = ImmutableMap.builder();
-    builder.put(Add.class, BinopDescriptor.ADD);
-    builder.put(Sub.class, BinopDescriptor.SUB);
+    builder.put(Add.class, ADD);
+    builder.put(Sub.class, SUB);
     builder.put(Mul.class, BinopDescriptor.MUL);
     builder.put(Div.class, BinopDescriptor.DIV);
     builder.put(Rem.class, BinopDescriptor.REM);
@@ -59,314 +61,6 @@
     return builder.build();
   }
 
-  /**
-   * A Binop descriptor describes left and right identity and absorbing element of binop. <code>
-   * In a space K, for a binop *:
-   * - i is left identity if for each x in K, i * x = x.
-   * - i is right identity if for each x in K, x * i = x.
-   * - a is left absorbing if for each x in K, a * x = a.
-   * - a is right absorbing if for each x in K, x * a = a.
-   * In a space K, a binop * is associative if for each x,y,z in K, (x * y) * z = x * (y * z).
-   * </code>
-   */
-  private enum BinopDescriptor {
-    ADD(true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return Add.create(numericType, dest, left, right);
-      }
-
-      @Override
-      Integer leftIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left + right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left + right;
-      }
-    },
-    SUB(false) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return new Sub(numericType, dest, left, right);
-      }
-
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left - right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left - right;
-      }
-    },
-    MUL(true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return Mul.create(numericType, dest, left, right);
-      }
-
-      @Override
-      Integer leftIdentity(boolean isBooleanValue) {
-        return 1;
-      }
-
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return 1;
-      }
-
-      @Override
-      Integer leftAbsorbing(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      Integer rightAbsorbing(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left * right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left * right;
-      }
-    },
-    // The following two can be improved if we handle ZeroDivide.
-    DIV(false) {
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return 1;
-      }
-    },
-    REM(false),
-    AND(true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return And.create(numericType, dest, left, right);
-      }
-
-      @Override
-      Integer leftIdentity(boolean isBooleanValue) {
-        return allBitsSet(isBooleanValue);
-      }
-
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return allBitsSet(isBooleanValue);
-      }
-
-      @Override
-      Integer leftAbsorbing(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      Integer rightAbsorbing(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left & right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left & right;
-      }
-    },
-    OR(true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return Or.create(numericType, dest, left, right);
-      }
-
-      @Override
-      Integer leftIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      Integer leftAbsorbing(boolean isBooleanValue) {
-        return allBitsSet(isBooleanValue);
-      }
-
-      @Override
-      Integer rightAbsorbing(boolean isBooleanValue) {
-        return allBitsSet(isBooleanValue);
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left | right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left | right;
-      }
-    },
-    XOR(true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return Xor.create(numericType, dest, left, right);
-      }
-
-      @Override
-      Integer leftIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left ^ right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left ^ right;
-      }
-    },
-    SHL(false) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return new Shl(numericType, dest, left, right);
-      }
-
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      Integer leftAbsorbing(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      boolean isShift() {
-        return true;
-      }
-    },
-    SHR(false) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return new Shr(numericType, dest, left, right);
-      }
-
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      Integer leftAbsorbing(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      boolean isShift() {
-        return true;
-      }
-    },
-    USHR(false) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return new Ushr(numericType, dest, left, right);
-      }
-
-      @Override
-      Integer rightIdentity(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      Integer leftAbsorbing(boolean isBooleanValue) {
-        return 0;
-      }
-
-      @Override
-      boolean isShift() {
-        return true;
-      }
-    };
-
-    final boolean associativeAndCommutative;
-
-    BinopDescriptor(
-        boolean associativeAndCommutative) {
-      this.associativeAndCommutative = associativeAndCommutative;
-    }
-
-    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-      throw new Unreachable();
-    }
-
-    Integer allBitsSet(boolean isBooleanValue) {
-      return isBooleanValue ? 1 : ALL_BITS_SET_MASK;
-    }
-
-    Integer leftIdentity(boolean isBooleanValue) {
-      return null;
-    }
-
-    Integer rightIdentity(boolean isBooleanValue) {
-      return null;
-    }
-
-    Integer leftAbsorbing(boolean isBooleanValue) {
-      return null;
-    }
-
-    Integer rightAbsorbing(boolean isBooleanValue) {
-      return null;
-    }
-
-    int evaluate(int left, int right) {
-      throw new Unreachable();
-    }
-
-    long evaluate(long left, long right) {
-      throw new Unreachable();
-    }
-
-    boolean isShift() {
-      return false;
-    }
-  }
-
   @Override
   protected String getRewriterId() {
     return "BinopRewriter";
@@ -391,7 +85,7 @@
             || binop.getNumericType() == NumericType.LONG) {
           BinopDescriptor binopDescriptor = descriptors.get(binop.getClass());
           assert binopDescriptor != null;
-          if (identityAbsorbingSimplification(iterator, binop, binopDescriptor)) {
+          if (identityAbsorbingSimplification(iterator, binop, binopDescriptor, code)) {
             hasChanged = true;
             continue;
           }
@@ -415,7 +109,7 @@
     ConstNumber constBRight = getConstNumber(binop.rightValue());
     if ((constBLeft != null && constBRight != null)
         || (constBLeft == null && constBRight == null)) {
-      return false;
+      return successiveLogicalSimplificationNoConstant(iterator, binop, binopDescriptor, code);
     }
     Value otherValue = constBLeft == null ? binop.leftValue() : binop.rightValue();
     if (otherValue.isPhi() || !otherValue.getDefinition().isBinop()) {
@@ -436,14 +130,14 @@
       if (binopDescriptor.associativeAndCommutative) {
         // a * x * b => x * (a * b) where (a * b) is a constant.
         assert binop.isCommutative();
-        Value newConst = addNewConstNumber(code, iterator, constB, constA, binopDescriptor);
-        replaceBinop(iterator, code, input, newConst, binopDescriptor);
+        rewriteIntoConstThenBinop(
+            iterator, binopDescriptor, binopDescriptor, constB, constA, input, true, code);
         return true;
       } else if (binopDescriptor.isShift()) {
         // x shift: a shift: b => x shift: (a + b) where a + b is a constant.
         if (constBRight != null && constARight != null) {
-          Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
-          replaceBinop(iterator, code, input, newConst, binopDescriptor);
+          rewriteIntoConstThenBinop(
+              iterator, ADD, binopDescriptor, constB, constA, input, false, code);
           return true;
         }
       } else if (binop.isSub() && constBRight != null) {
@@ -451,12 +145,10 @@
         // x - a - b => x - (a + b) where (a + b) is a constant.
         // We ignore b - (x - a) and b - (a - x) with constBRight != null.
         if (constARight == null) {
-          Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
-          replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+          rewriteIntoConstThenBinop(iterator, SUB, SUB, constA, constB, input, true, code);
           return true;
         } else {
-          Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
-          replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+          rewriteIntoConstThenBinop(iterator, ADD, SUB, constB, constA, input, false, code);
           return true;
         }
       }
@@ -465,19 +157,16 @@
         // x + a - b => x + (a - b) where (a - b) is a constant.
         // a + x - b => x + (a - b) where (a - b) is a constant.
         // We ignore b - (x + a) and b - (a + x) with constBRight != null.
-        Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
-        replaceBinop(iterator, code, newConst, input, BinopDescriptor.ADD);
+        rewriteIntoConstThenBinop(iterator, SUB, ADD, constA, constB, input, true, code);
         return true;
       } else if (binop.isAdd() && prevBinop.isSub()) {
         // x - a + b => x - (a - b) where (a - b) is a constant.
         // a - x + b => (a + b) - x where (a + b) is a constant.
         if (constALeft == null) {
-          Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
-          replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+          rewriteIntoConstThenBinop(iterator, SUB, SUB, constA, constB, input, false, code);
           return true;
         } else {
-          Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
-          replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+          rewriteIntoConstThenBinop(iterator, ADD, SUB, constB, constA, input, true, code);
           return true;
         }
       }
@@ -485,6 +174,162 @@
     return false;
   }
 
+  private boolean successiveLogicalSimplificationNoConstant(
+      InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor, IRCode code) {
+    if (!(binop.isAnd() || binop.isOr())) {
+      return false;
+    }
+    if (binop.leftValue().isPhi() || binop.rightValue().isPhi()) {
+      return false;
+    }
+    LogicalBinop leftDef = binop.leftValue().getDefinition().asLogicalBinop();
+    LogicalBinop rightDef = binop.rightValue().getDefinition().asLogicalBinop();
+    if (leftDef == null
+        || rightDef == null
+        || (leftDef.getClass() != rightDef.getClass())
+        || (leftDef.getNumericType() != rightDef.getNumericType())) {
+      return false;
+    }
+    // These optimizations were implemented mostly to deal with Compose specific bit patterns.
+    if (leftDef.isAnd() || leftDef.isOr()) {
+      return andOrOnCommonInputSimplification(
+          iterator, binop, leftDef, rightDef, binopDescriptor, code);
+    }
+    if (leftDef.isShl() || leftDef.isShr() || leftDef.isUshr()) {
+      return shiftOnCommonValueSharing(iterator, binop, leftDef, rightDef, binopDescriptor, code);
+    }
+    return false;
+  }
+
+  private boolean andOrOnCommonInputSimplification(
+      InstructionListIterator iterator,
+      Instruction binop,
+      LogicalBinop leftDef,
+      LogicalBinop rightDef,
+      BinopDescriptor binopDescriptor,
+      IRCode code) {
+    // For all permutations of & and |, represented by &| and |&.
+    // (x &| a) |& (x &| b) => (a |& b) &| x.
+    // a |& b will be simplified into a new constant if both constant.
+    Value x, a, b;
+    if (leftDef.leftValue() == rightDef.leftValue()) {
+      x = leftDef.leftValue();
+      a = leftDef.rightValue();
+      b = rightDef.rightValue();
+    } else if (leftDef.leftValue() == rightDef.rightValue()) {
+      x = leftDef.leftValue();
+      a = leftDef.rightValue();
+      b = rightDef.leftValue();
+    } else if (leftDef.rightValue() == rightDef.leftValue()) {
+      x = leftDef.rightValue();
+      a = leftDef.leftValue();
+      b = rightDef.rightValue();
+    } else if (leftDef.rightValue() == rightDef.rightValue()) {
+      x = leftDef.rightValue();
+      a = leftDef.leftValue();
+      b = rightDef.leftValue();
+    } else {
+      return false;
+    }
+
+    rewriteIntoTwoSuccessiveBinops(
+        iterator,
+        binop.getPosition(),
+        binopDescriptor,
+        descriptors.get(leftDef.getClass()),
+        a,
+        b,
+        x,
+        code);
+    return true;
+  }
+
+  private boolean shiftOnCommonValueSharing(
+      InstructionListIterator iterator,
+      Instruction binop,
+      LogicalBinop leftDef,
+      LogicalBinop rightDef,
+      BinopDescriptor binopDescriptor,
+      IRCode code) {
+    // For all permutations of & and |, represented by &|, and any shift operation.
+    // (x shift: val) &| (y shift: val) => (x &| y) shift: val.
+    // x |& y will be simplified into a new constant if both constant.
+    ConstNumber constLeft = getConstNumber(leftDef.rightValue());
+    if (constLeft != null) {
+      // val is a constant.
+      ConstNumber constRight = getConstNumber(rightDef.rightValue());
+      if (constRight == null) {
+        return false;
+      }
+      if (constRight.getRawValue() != constLeft.getRawValue()) {
+        return false;
+      }
+    } else {
+      // val is not constant.
+      if (leftDef.rightValue() != rightDef.rightValue()) {
+        return false;
+      }
+    }
+
+    rewriteIntoTwoSuccessiveBinops(
+        iterator,
+        binop.getPosition(),
+        binopDescriptor,
+        descriptors.get(leftDef.getClass()),
+        leftDef.leftValue(),
+        rightDef.leftValue(),
+        leftDef.rightValue(),
+        code);
+    return true;
+  }
+
+  private void rewriteIntoTwoSuccessiveBinops(
+      InstructionListIterator iterator,
+      Position position,
+      BinopDescriptor firstBinop,
+      BinopDescriptor secondBinop,
+      Value firstLeft,
+      Value firstRight,
+      Value secondRight,
+      IRCode code) {
+    // This creates something along the lines of:
+    // `(firstLeft firstBinop: firstRight) secondBinop: secondOther`.
+    ConstNumber constA = getConstNumber(firstLeft);
+    if (constA != null) {
+      ConstNumber constB = getConstNumber(firstRight);
+      if (constB != null) {
+        rewriteIntoConstThenBinop(
+            iterator, firstBinop, secondBinop, constA, constB, secondRight, true, code);
+        return;
+      }
+    }
+    Binop newFirstBinop = instantiateBinop(code, firstLeft, firstRight, firstBinop);
+    newFirstBinop.setPosition(position);
+    iterator.previous();
+    iterator.add(newFirstBinop);
+    iterator.next();
+    replaceBinop(iterator, code, newFirstBinop.outValue(), secondRight, secondBinop);
+    iterator.previous();
+  }
+
+  private void rewriteIntoConstThenBinop(
+      InstructionListIterator iterator,
+      BinopDescriptor firstBinop,
+      BinopDescriptor secondBinop,
+      ConstNumber firstLeft,
+      ConstNumber firstRight,
+      Value secondOther,
+      boolean newConstFlowsIntoLeft,
+      IRCode code) {
+    Value firstOutValue = insertNewConstNumber(code, iterator, firstLeft, firstRight, firstBinop);
+    replaceBinop(
+        iterator,
+        code,
+        newConstFlowsIntoLeft ? firstOutValue : secondOther,
+        newConstFlowsIntoLeft ? secondOther : firstOutValue,
+        secondBinop);
+  }
+
   private void replaceBinop(
       InstructionListIterator iterator,
       IRCode code,
@@ -493,11 +338,8 @@
       BinopDescriptor binopDescriptor) {
     Binop newBinop = instantiateBinop(code, left, right, binopDescriptor);
     iterator.replaceCurrentInstruction(newBinop);
-    // We need to reset the iterator state after replaceCurrentInstruction so that Iterator#remove()
-    // can work in identityAbsorbingSimplification by calling previous then next.
+    // We need to reset the iterator state to process the new instruction(s).
     iterator.previous();
-    iterator.next();
-    identityAbsorbingSimplification(iterator, newBinop, binopDescriptor);
   }
 
   private Binop instantiateBinop(IRCode code, Value left, Value right, BinopDescriptor descriptor) {
@@ -507,7 +349,7 @@
     return descriptor.instantiate(numericType, newValue, left, right);
   }
 
-  private Value addNewConstNumber(
+  private Value insertNewConstNumber(
       IRCode code,
       InstructionListIterator iterator,
       ConstNumber left,
@@ -528,7 +370,7 @@
   }
 
   private boolean identityAbsorbingSimplification(
-      InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor) {
+      InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor, IRCode code) {
     ConstNumber constNumber = getConstNumber(binop.leftValue());
     if (constNumber != null) {
       boolean isBooleanValue = binop.outValue().knownToBeBoolean();
@@ -546,14 +388,28 @@
     constNumber = getConstNumber(binop.rightValue());
     if (constNumber != null) {
       boolean isBooleanValue = binop.outValue().knownToBeBoolean();
-      return simplify(
+      if (simplify(
           binop,
           iterator,
           constNumber,
           binopDescriptor.rightIdentity(isBooleanValue),
           binop.leftValue(),
           binopDescriptor.rightAbsorbing(isBooleanValue),
-          binop.rightValue());
+          binop.rightValue())) {
+        return true;
+      }
+    }
+    if (binop.leftValue() == binop.rightValue()) {
+      if (binop.isXor() || binop.isSub()) {
+        // a ^ a => 0, a - a => 0
+        ConstNumber zero = new ConstNumber(code.createValue(binop.outValue().getType()), 0);
+        iterator.replaceCurrentInstruction(zero);
+      } else if (binop.isAnd() || binop.isOr()) {
+        // a & a => a, a | a => a.
+        binop.outValue().replaceUsers(binop.leftValue());
+        iterator.remove();
+      }
+      return true;
     }
     return false;
   }
diff --git a/src/test/java/com/android/tools/r8/ir/ComposeBinopTest.java b/src/test/java/com/android/tools/r8/ir/ComposeBinopTest.java
new file mode 100644
index 0000000..cd41b8d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/ComposeBinopTest.java
@@ -0,0 +1,319 @@
+// 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.ir;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ComposeBinopTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "12345678",
+          "12345678",
+          "0",
+          "0",
+          "76532",
+          "76532",
+          "0",
+          "0",
+          "-2147483648",
+          "-2147483648",
+          "0",
+          "0",
+          "74",
+          "0",
+          "12345838",
+          "12345854",
+          "240",
+          "160",
+          "76532",
+          "76542",
+          "0",
+          "0",
+          "-2147483488",
+          "-2147483398",
+          "8260",
+          "0",
+          "12345678",
+          "-2135069698",
+          "8260",
+          "0",
+          "76532",
+          "-2135069698",
+          "0",
+          "0",
+          "-2147475388",
+          "-2135069698",
+          "0",
+          "0",
+          "3948544",
+          "1572864",
+          "0",
+          "0",
+          "0",
+          "0",
+          "252706816",
+          "100663296",
+          "0",
+          "0",
+          "255",
+          "80",
+          "241",
+          "96",
+          "243",
+          "96",
+          "1551743",
+          "1032",
+          "99311600",
+          "66080",
+          "1551743",
+          "1032",
+          "-266892247",
+          "0",
+          "98765424",
+          "0",
+          "269978665",
+          "0",
+          "-268425890",
+          "0",
+          "612256",
+          "0",
+          "268445022",
+          "0",
+          "12413950",
+          "8260",
+          "12413950",
+          "8260",
+          "12413950",
+          "8260",
+          "-131068",
+          "0",
+          "1253900288",
+          "0",
+          "131076",
+          "0",
+          "-2037",
+          "0",
+          "350224384",
+          "0",
+          "2059",
+          "0",
+          "41",
+          "350",
+          "0");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ComposeBinopTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject mainClass = inspector.clazz(Main.class);
+    assertTrue(mainClass.isPresent());
+
+    assertTrue(
+        mainClass
+            .uniqueMethodWithOriginalName("bitSameInput")
+            .streamInstructions()
+            .noneMatch(InstructionSubject::isIntLogicalBinop));
+    assertEquals(
+        6,
+        mainClass
+            .uniqueMethodWithOriginalName("shareShiftCstLeft")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        12,
+        mainClass
+            .uniqueMethodWithOriginalName("shareShiftCstRight")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        12,
+        mainClass
+            .uniqueMethodWithOriginalName("shareShiftVar")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        4,
+        mainClass
+            .uniqueMethodWithOriginalName("andOrCompositionCst")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        8,
+        mainClass
+            .uniqueMethodWithOriginalName("andOrCompositionVar")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        2,
+        mainClass
+            .uniqueMethodWithOriginalName("composeTests")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      int i1 = System.currentTimeMillis() > 0 ? 12345678 : 1;
+      int i2 = System.currentTimeMillis() > 0 ? 76532 : 1;
+      int i3 = System.currentTimeMillis() > 0 ? Integer.MIN_VALUE : 1;
+
+      bitSameInput(i1);
+      bitSameInput(i2);
+      bitSameInput(i3);
+
+      andOrCompositionCst(i1);
+      andOrCompositionCst(i2);
+      andOrCompositionCst(i3);
+
+      andOrCompositionVar(i1, i2, i3);
+      andOrCompositionVar(i2, i3, i1);
+      andOrCompositionVar(i3, i1, i2);
+
+      shareShiftCstLeft(i1);
+      shareShiftCstLeft(i2);
+      shareShiftCstLeft(i3);
+
+      shareShiftCstRight(i1, i2);
+      shareShiftCstRight(i1, i3);
+      shareShiftCstRight(i2, i3);
+
+      shareShiftVar(i1, i2, i3);
+      shareShiftVar(i2, i3, i1);
+      shareShiftVar(i3, i1, i2);
+
+      composeTests(i1);
+      composeTests(i2);
+      composeTests(i3);
+    }
+
+    @NeverInline
+    private static void bitSameInput(int a) {
+      // a & a => a, a | a => a.
+      System.out.println(a & a);
+      System.out.println(a | a);
+      // a ^ a => 0, a - a => 0
+      System.out.println(a ^ a);
+      System.out.println(a - a);
+    }
+
+    @NeverInline
+    private static void shareShiftCstRight(int a, int b) {
+      // (x shift: val) | (y shift: val) => (x | y) shift: val.
+      // (x shift: val) & (y shift: val) => (x & y) shift: val.
+      System.out.println((a >> 3) | (b >> 3));
+      System.out.println((a >> 3) & (b >> 3));
+      System.out.println((a << 3) | (b << 3));
+      System.out.println((a << 3) & (b << 3));
+      System.out.println((a >>> 3) | (b >>> 3));
+      System.out.println((a >>> 3) & (b >>> 3));
+    }
+
+    @NeverInline
+    private static void shareShiftCstLeft(int a) {
+      // (x shift: val) | (y shift: val) => (x | y) shift: val.
+      // (x shift: val) & (y shift: val) => (x & y) shift: val.
+      System.out.println((111 >> a) | (222 >> a));
+      System.out.println((112 >> a) & (223 >> a));
+      System.out.println((113 << a) | (224 << a));
+      System.out.println((114 << a) & (225 << a));
+      System.out.println((115 >>> a) | (226 >>> a));
+      System.out.println((116 >>> a) & (227 >>> a));
+    }
+
+    @NeverInline
+    private static void shareShiftVar(int a, int b, int c) {
+      // (x shift: val) | (y shift: val) => (x | y) shift: val.
+      // (x shift: val) & (y shift: val) => (x & y) shift: val.
+      System.out.println((a >> c) | (b >> c));
+      System.out.println((a >> c) & (b >> c));
+      System.out.println((a << c) | (b << c));
+      System.out.println((a << c) & (b << c));
+      System.out.println((a >>> c) | (b >>> c));
+      System.out.println((a >>> c) & (b >>> c));
+    }
+
+    @NeverInline
+    private static void andOrCompositionCst(int a) {
+      // For all permutations of & and |, represented by &| and |&.
+      // (x &| a) |& (x &| b) => x &| (a |& b).
+      // Note that here a and b are constants therefore should be resolved at compile-time.
+      System.out.println((a & 0b10101010) | (a & 0b11110000));
+      System.out.println((a & 0b10101010) & (a & 0b11110000));
+      System.out.println((a | 0b10101010) & (a | 0b11110000));
+      System.out.println((a | 0b10101010) | (a | 0b11110000));
+    }
+
+    @NeverInline
+    private static void andOrCompositionVar(int a, int b, int c) {
+      // For all permutations of & and |.
+      // (x &| a) |& (x &| b) => x &| (a |& b).
+      System.out.println((a & b) | (a & c));
+      System.out.println((a & b) & (a & c));
+      System.out.println((a | b) & (a | c));
+      System.out.println((a | b) | (a | c));
+    }
+
+    @NeverInline
+    private static void composeTests(int dirty) {
+      // This is rewritten to ((0b0001111111111110 & dirty) >> 3).
+      System.out.println(
+          ((0b1110 & dirty) >> 3)
+              | ((0b01110000 & dirty) >> 3)
+              | ((0b001110000000 & dirty) >> 3)
+              | ((0b0001110000000000 & dirty) >> 3));
+    }
+  }
+}