Fix shifts in AbstractCalculator

Bug: b/348499741
Change-Id: Ief9d63fae9a981d2e9800ccab8d8766abb984184
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java b/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java
index ed6c059..9b37213 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.value.arithmetic;
 
+import static com.android.tools.r8.utils.BitUtils.INTEGER_SHIFT_MASK;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.utils.BitUtils;
@@ -96,7 +98,8 @@
     return shlIntegers(appView, left, rightConst);
   }
 
-  public static AbstractValue shlIntegers(AppView<?> appView, AbstractValue left, int rightConst) {
+  public static AbstractValue shlIntegers(AppView<?> appView, AbstractValue left, int right) {
+    int rightConst = right & INTEGER_SHIFT_MASK;
     if (rightConst == 0) {
       return left;
     }
@@ -104,7 +107,7 @@
       int result = left.asSingleNumberValue().getIntValue() << rightConst;
       return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
     }
-    if (left.hasDefinitelySetAndUnsetBitsInformation() && rightConst > 0) {
+    if (left.hasDefinitelySetAndUnsetBitsInformation()) {
       // Shift the known bits and add that we now know that the lowermost n bits are definitely
       // unset. Note that when rightConst is 31, 1 << rightConst is Integer.MIN_VALUE. When
       // subtracting 1 we overflow and get 0111...111, as desired.
@@ -126,7 +129,8 @@
     return shrIntegers(appView, left, rightConst);
   }
 
-  public static AbstractValue shrIntegers(AppView<?> appView, AbstractValue left, int rightConst) {
+  public static AbstractValue shrIntegers(AppView<?> appView, AbstractValue left, int right) {
+    int rightConst = right & INTEGER_SHIFT_MASK;
     if (rightConst == 0) {
       return left;
     }
@@ -149,7 +153,7 @@
     if (!right.isSingleNumberValue()) {
       return AbstractValue.unknown();
     }
-    int rightConst = right.asSingleNumberValue().getIntValue();
+    int rightConst = right.asSingleNumberValue().getIntValue() & INTEGER_SHIFT_MASK;
     if (rightConst == 0) {
       return left;
     }
@@ -157,7 +161,7 @@
       int result = left.asSingleNumberValue().getIntValue() >>> rightConst;
       return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
     }
-    if (left.hasDefinitelySetAndUnsetBitsInformation() && rightConst > 0) {
+    if (left.hasDefinitelySetAndUnsetBitsInformation()) {
       // Shift the known bits information and add that we now know that the uppermost n bits are
       // definitely unset.
       return appView
diff --git a/src/main/java/com/android/tools/r8/utils/BitUtils.java b/src/main/java/com/android/tools/r8/utils/BitUtils.java
index 3a391e4..b6945ae 100644
--- a/src/main/java/com/android/tools/r8/utils/BitUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BitUtils.java
@@ -8,6 +8,7 @@
 
   public static final int ALL_BITS_SET_MASK = -1;
   public static final int ONLY_SIGN_BIT_SET_MASK = Integer.MIN_VALUE;
+  public static final int INTEGER_SHIFT_MASK = 0x1f;
 
   public static boolean isBitSet(int value, int which) {
     return isBitInMaskSet(value, 1 << (which - 1));
diff --git a/src/test/java/com/android/tools/r8/ir/ShiftIssueTest.java b/src/test/java/com/android/tools/r8/ir/ShiftIssueTest.java
new file mode 100644
index 0000000..e535d3e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/ShiftIssueTest.java
@@ -0,0 +1,121 @@
+// 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 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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ShiftIssueTest extends TestBase {
+
+  private static final String EXPECTED_RESULT = StringUtils.lines("7", "7", "7", "3", "3", "14");
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ShiftIssueTest(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)
+        .allowStdoutMessages()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      ushr32();
+      shr32();
+      shl32();
+      ushr33();
+      shr33();
+      shl33();
+    }
+
+    @NeverInline
+    private static void ushr32() {
+      int d = 1;
+      for (int g = 0; g < 10; g++) {
+        d = 7;
+      }
+      d >>>= 32;
+      System.out.println(d);
+    }
+
+    @NeverInline
+    private static void shr32() {
+      int d = 1;
+      for (int g = 0; g < 10; g++) {
+        d = 7;
+      }
+      d >>= 32;
+      System.out.println(d);
+    }
+
+    @NeverInline
+    private static void shl32() {
+      int d = 1;
+      for (int g = 0; g < 10; g++) {
+        d = 7;
+      }
+      d <<= 32;
+      System.out.println(d);
+    }
+
+    @NeverInline
+    private static void ushr33() {
+      int d = 1;
+      for (int g = 0; g < 10; g++) {
+        d = 7;
+      }
+      d >>>= 33;
+      System.out.println(d);
+    }
+
+    @NeverInline
+    private static void shr33() {
+      int d = 1;
+      for (int g = 0; g < 10; g++) {
+        d = 7;
+      }
+      d >>= 33;
+      System.out.println(d);
+    }
+
+    @NeverInline
+    private static void shl33() {
+      int d = 1;
+      for (int g = 0; g < 10; g++) {
+        d = 7;
+      }
+      d <<= 33;
+      System.out.println(d);
+    }
+  }
+}