[llvm] [AArch64][GlobalISel] Add known bits for G_DUP, G_VASHR and G_VLSHR (PR #210503)

Joel Walker via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 13 16:02:05 PDT 2026


https://github.com/Joel-Wwalker updated https://github.com/llvm/llvm-project/pull/210503

>From f31a56b7d284f4cd2629cd92af715e6765d36e81 Mon Sep 17 00:00:00 2001
From: Joel-Wwalker <theagingboy05 at gmail.com>
Date: Sat, 18 Jul 2026 07:38:04 -0400
Subject: [PATCH 1/2] [AArch64][GlobalISel] Add known bits for G_DUP, G_VASHR
 and G_VLSHR

Adds AArch64's computeKnownBitsForTargetInstr hook (previously absent,
unlike its SelectionDAG counterpart) with the first three cases:

- G_DUP: the known bits of the scalar operand, truncated to the element
  width when the operand is implicitly truncated.
- G_VASHR/G_VLSHR: KnownBits::ashr/lshr of the source by the immediate.
  A shift by the full element width is legal for these instructions but
  is poison for the IR shifts the KnownBits helpers model, so that case
  conservatively stays unknown.

All three mirror the corresponding cases in computeKnownBitsForTargetNode.
The arm64-neon-aba-abd.ll changes fold the knownbits_sabd test functions
to constants, matching what SelectionDAG already proves; the
shuffle-slide-to-shift.ll change drops a redundant zero materialization.

Follow-up to #210269.
---
 .../Target/AArch64/AArch64ISelLowering.cpp    | 40 +++++++++++++++++++
 llvm/lib/Target/AArch64/AArch64ISelLowering.h |  6 +++
 .../AArch64/GlobalISel/knownbits-dup.mir      | 35 ++++++++++++++++
 .../AArch64/GlobalISel/knownbits-vashr.mir    | 18 +++++++++
 .../AArch64/GlobalISel/knownbits-vlshr.mir    | 33 +++++++++++++++
 .../CodeGen/AArch64/arm64-neon-aba-abd.ll     | 11 +----
 .../CodeGen/AArch64/shuffle-slide-to-shift.ll |  6 +--
 7 files changed, 136 insertions(+), 13 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/GlobalISel/knownbits-dup.mir
 create mode 100644 llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vlshr.mir

diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 99bcb5f50c134..a11180be006b3 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -3162,6 +3162,46 @@ unsigned AArch64TargetLowering::ComputeNumSignBitsForTargetNode(
   return 1;
 }
 
+void AArch64TargetLowering::computeKnownBitsForTargetInstr(
+    GISelValueTracking &Analysis, Register R, KnownBits &Known,
+    const APInt &DemandedElts, const MachineRegisterInfo &MRI,
+    unsigned Depth) const {
+  const MachineInstr *MI = MRI.getVRegDef(R);
+  switch (MI->getOpcode()) {
+  case AArch64::G_DUP: {
+    Register Src = MI->getOperand(1).getReg();
+    unsigned SrcBits = MRI.getType(Src).getSizeInBits();
+    KnownBits SrcKnown(SrcBits);
+    Analysis.computeKnownBitsImpl(Src, SrcKnown, APInt(1, 1), Depth + 1);
+    unsigned EltBits = MRI.getType(R).getScalarSizeInBits();
+    if (SrcBits != EltBits) {
+      assert(SrcBits > EltBits && "Expected DUP implicit truncation");
+      SrcKnown = SrcKnown.trunc(EltBits);
+    }
+    Known = SrcKnown;
+    break;
+  }
+  case AArch64::G_VASHR:
+  case AArch64::G_VLSHR: {
+    unsigned BitWidth = MRI.getType(R).getScalarSizeInBits();
+    uint64_t Shift = MI->getOperand(2).getImm();
+    // A shift by the full element width is legal for these instructions, but
+    // KnownBits::ashr/lshr model IR shifts, for which it is poison. Leave the
+    // result unknown in that case.
+    if (Shift >= BitWidth)
+      break;
+    KnownBits SrcKnown(BitWidth);
+    Analysis.computeKnownBitsImpl(MI->getOperand(1).getReg(), SrcKnown,
+                                  DemandedElts, Depth + 1);
+    KnownBits ShiftKnown = KnownBits::makeConstant(APInt(BitWidth, Shift));
+    Known = MI->getOpcode() == AArch64::G_VASHR
+                ? KnownBits::ashr(SrcKnown, ShiftKnown)
+                : KnownBits::lshr(SrcKnown, ShiftKnown);
+    break;
+  }
+  }
+}
+
 unsigned AArch64TargetLowering::computeNumSignBitsForTargetInstr(
     GISelValueTracking &Analysis, Register R, const APInt &DemandedElts,
     const MachineRegisterInfo &MRI, unsigned Depth) const {
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.h b/llvm/lib/Target/AArch64/AArch64ISelLowering.h
index 1b622e93480ec..f100627b6f143 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.h
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.h
@@ -91,6 +91,12 @@ class AArch64TargetLowering : public TargetLowering {
                                            const SelectionDAG &DAG,
                                            unsigned Depth) const override;
 
+  void computeKnownBitsForTargetInstr(GISelValueTracking &Analysis, Register R,
+                                      KnownBits &Known,
+                                      const APInt &DemandedElts,
+                                      const MachineRegisterInfo &MRI,
+                                      unsigned Depth = 0) const override;
+
   unsigned computeNumSignBitsForTargetInstr(GISelValueTracking &Analysis,
                                             Register R,
                                             const APInt &DemandedElts,
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-dup.mir b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-dup.mir
new file mode 100644
index 0000000000000..b2fd83ed5b1f4
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-dup.mir
@@ -0,0 +1,35 @@
+# NOTE: Assertions have been autogenerated by utils/update_givaluetracking_test_checks.py UTC_ARGS: --version 5
+# RUN: llc -mtriple=aarch64 -passes="print<gisel-value-tracking>" -filetype=null %s 2>&1 | FileCheck %s
+
+---
+name: DUP
+body: |
+  bb.1:
+    liveins: $w0
+  ; CHECK-LABEL: name: @DUP
+  ; CHECK-NEXT: %0:_ KnownBits:???????????????????????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:00000000000000000000000000001111 SignBits:28 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:_ KnownBits:0000000000000000000000000000???? SignBits:28 IsKnownNeverZero:0
+  ; CHECK-NEXT: %3:fpr KnownBits:0000000000000000000000000000???? SignBits:28 IsKnownNeverZero:0
+    %0:_(i32) = COPY $w0
+    %1:_(i32) = G_CONSTANT i32 15
+    %2:_(i32) = G_AND %0, %1
+    %3:fpr(<4 x i32>) = G_DUP %2(i32)
+    $q0 = COPY %3:fpr(<4 x i32>)
+...
+---
+name: DUP_implicit_trunc
+body: |
+  bb.1:
+    liveins: $w0
+  ; CHECK-LABEL: name: @DUP_implicit_trunc
+  ; CHECK-NEXT: %0:_ KnownBits:???????????????????????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:00000000000000000000000000001000 SignBits:28 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:_ KnownBits:????????????????????????????1??? SignBits:1 IsKnownNeverZero:1
+  ; CHECK-NEXT: %3:fpr KnownBits:????????????1??? SignBits:1 IsKnownNeverZero:1
+    %0:_(i32) = COPY $w0
+    %1:_(i32) = G_CONSTANT i32 8
+    %2:_(i32) = G_OR %0, %1
+    %3:fpr(<8 x i16>) = G_DUP %2(i32)
+    $q0 = COPY %3:fpr(<8 x i16>)
+...
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vashr.mir b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vashr.mir
index 8096e3729c30b..dd957d3e24a4c 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vashr.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vashr.mir
@@ -55,3 +55,21 @@ body: |
     %3:fpr(<4 x i32>) = G_VASHR %2, 5
     $q0 = COPY %3:fpr(<4 x i32>)
 ...
+---
+name: VASHR_known_sign_bits
+body: |
+  bb.1:
+    liveins: $q0
+  ; CHECK-LABEL: name: @VASHR_known_sign_bits
+  ; CHECK-NEXT: %0:fpr KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:1000000000000000 SignBits:1 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:fpr KnownBits:1000000000000000 SignBits:1 IsKnownNeverZero:1
+  ; CHECK-NEXT: %3:fpr KnownBits:1??????????????? SignBits:1 IsKnownNeverZero:1
+  ; CHECK-NEXT: %4:fpr KnownBits:11111??????????? SignBits:5 IsKnownNeverZero:1
+    %0:fpr(<8 x i16>) = COPY $q0
+    %1:_(i16) = G_CONSTANT i16 -32768
+    %2:fpr(<8 x i16>) = G_BUILD_VECTOR %1, %1, %1, %1, %1, %1, %1, %1
+    %3:fpr(<8 x i16>) = G_OR %0, %2
+    %4:fpr(<8 x i16>) = G_VASHR %3, 4
+    $q0 = COPY %4:fpr(<8 x i16>)
+...
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vlshr.mir b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vlshr.mir
new file mode 100644
index 0000000000000..9b75766fc9ee3
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vlshr.mir
@@ -0,0 +1,33 @@
+# NOTE: Assertions have been autogenerated by utils/update_givaluetracking_test_checks.py UTC_ARGS: --version 5
+# RUN: llc -mtriple=aarch64 -passes="print<gisel-value-tracking>" -filetype=null %s 2>&1 | FileCheck %s
+
+---
+name: VLSHR
+body: |
+  bb.1:
+    liveins: $q0
+  ; CHECK-LABEL: name: @VLSHR
+  ; CHECK-NEXT: %0:fpr KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:0000000011110000 SignBits:8 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:fpr KnownBits:0000000011110000 SignBits:8 IsKnownNeverZero:1
+  ; CHECK-NEXT: %3:fpr KnownBits:00000000????0000 SignBits:8 IsKnownNeverZero:0
+  ; CHECK-NEXT: %4:fpr KnownBits:000000000000???? SignBits:12 IsKnownNeverZero:0
+    %0:fpr(<8 x i16>) = COPY $q0
+    %1:_(i16) = G_CONSTANT i16 240
+    %2:fpr(<8 x i16>) = G_BUILD_VECTOR %1, %1, %1, %1, %1, %1, %1, %1
+    %3:fpr(<8 x i16>) = G_AND %0, %2
+    %4:fpr(<8 x i16>) = G_VLSHR %3, 4
+    $q0 = COPY %4:fpr(<8 x i16>)
+...
+---
+name: VLSHR_by_element_width
+body: |
+  bb.1:
+    liveins: $q0
+  ; CHECK-LABEL: name: @VLSHR_by_element_width
+  ; CHECK-NEXT: %0:fpr KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:fpr KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+    %0:fpr(<8 x i16>) = COPY $q0
+    %1:fpr(<8 x i16>) = G_VLSHR %0, 16
+    $q0 = COPY %1:fpr(<8 x i16>)
+...
diff --git a/llvm/test/CodeGen/AArch64/arm64-neon-aba-abd.ll b/llvm/test/CodeGen/AArch64/arm64-neon-aba-abd.ll
index 8adf687b8e20d..467720f3eea24 100644
--- a/llvm/test/CodeGen/AArch64/arm64-neon-aba-abd.ll
+++ b/llvm/test/CodeGen/AArch64/arm64-neon-aba-abd.ll
@@ -576,16 +576,9 @@ define <4 x i32> @knownbits_sabd_and_shl_mask(<4 x i32> %a0, <4 x i32> %a1) {
 ;
 ; CHECK-GI-LABEL: knownbits_sabd_and_shl_mask:
 ; CHECK-GI:       // %bb.0:
-; CHECK-GI-NEXT:    adrp x8, .LCPI47_1
-; CHECK-GI-NEXT:    ldr q2, [x8, :lo12:.LCPI47_1]
 ; CHECK-GI-NEXT:    adrp x8, .LCPI47_0
-; CHECK-GI-NEXT:    and v0.16b, v0.16b, v2.16b
-; CHECK-GI-NEXT:    and v1.16b, v1.16b, v2.16b
-; CHECK-GI-NEXT:    shl v0.4s, v0.4s, #17
-; CHECK-GI-NEXT:    shl v1.4s, v1.4s, #17
-; CHECK-GI-NEXT:    sabd v0.4s, v0.4s, v1.4s
-; CHECK-GI-NEXT:    ldr q1, [x8, :lo12:.LCPI47_0]
-; CHECK-GI-NEXT:    tbl v0.16b, { v0.16b }, v1.16b
+; CHECK-GI-NEXT:    ldr q0, [x8, :lo12:.LCPI47_0]
+; CHECK-GI-NEXT:    tbl v0.16b, { v0.16b }, v0.16b
 ; CHECK-GI-NEXT:    ret
   %1 = and <4 x i32> %a0, <i32 -65536, i32 -7, i32 -7, i32 -65536>
   %2 = shl <4 x i32> %1, <i32 17, i32 17, i32 17, i32 17>
diff --git a/llvm/test/CodeGen/AArch64/shuffle-slide-to-shift.ll b/llvm/test/CodeGen/AArch64/shuffle-slide-to-shift.ll
index b9d70a98553da..7ae32d6678951 100644
--- a/llvm/test/CodeGen/AArch64/shuffle-slide-to-shift.ll
+++ b/llvm/test/CodeGen/AArch64/shuffle-slide-to-shift.ll
@@ -156,12 +156,10 @@ define <4 x i8> @slide_right_v4i8(<4 x i8> %v) {
 ;
 ; CHECK-GI-LABEL: slide_right_v4i8:
 ; CHECK-GI:       // %bb.0:
-; CHECK-GI-NEXT:    movi v1.2d, #0000000000000000
 ; CHECK-GI-NEXT:    uzp1 v0.8b, v0.8b, v0.8b
 ; CHECK-GI-NEXT:    adrp x8, .LCPI8_0
-; CHECK-GI-NEXT:    mov v1.d[1], v0.d[0]
-; CHECK-GI-NEXT:    ldr d0, [x8, :lo12:.LCPI8_0]
-; CHECK-GI-NEXT:    tbl v0.16b, { v1.16b }, v0.16b
+; CHECK-GI-NEXT:    ldr d1, [x8, :lo12:.LCPI8_0]
+; CHECK-GI-NEXT:    tbl v0.16b, { v0.16b }, v1.16b
 ; CHECK-GI-NEXT:    zip1 v0.8b, v0.8b, v0.8b
 ; CHECK-GI-NEXT:    ret
   %r = shufflevector <4 x i8> zeroinitializer, <4 x i8> %v,

>From 2b7dee9dd1bca575e8bac82fce17958ab2c03db8 Mon Sep 17 00:00:00 2001
From: Joel-Wwalker <theagingboy05 at gmail.com>
Date: Sun, 13 Sep 2026 18:43:27 -0400
Subject: [PATCH 2/2] [AArch64][GlobalISel] Model full-width G_VASHR/G_VLSHR in
 known bits

A shift by the full element width is legal for these instructions, and
GlobalISel emits it at -O0: `ashr <4 x i32> %x, splat (i32 32)` becomes
`G_VASHR %x, 32` and then `sshr v0.4s, v0.4s, #32`. KnownBits::ashr
models the IR shift, for which that amount is poison, and returns
all-zero for it; sshr fills the lane with the sign bit instead. Clamp
the sshr amount to BitWidth - 1, which produces the same lane, rather
than leaving the result unknown. ushr by the full width gives zero,
which KnownBits::lshr already returns, so the pinned test now expects
zero.

Assisted by Claude (Anthropic).
---
 .../Target/AArch64/AArch64ISelLowering.cpp    | 11 +++---
 .../AArch64/GlobalISel/knownbits-vashr.mir    | 36 +++++++++++++++++++
 .../AArch64/GlobalISel/knownbits-vlshr.mir    |  2 +-
 3 files changed, 43 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index a11180be006b3..f84bb2a5b6c3a 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -3185,11 +3185,12 @@ void AArch64TargetLowering::computeKnownBitsForTargetInstr(
   case AArch64::G_VLSHR: {
     unsigned BitWidth = MRI.getType(R).getScalarSizeInBits();
     uint64_t Shift = MI->getOperand(2).getImm();
-    // A shift by the full element width is legal for these instructions, but
-    // KnownBits::ashr/lshr model IR shifts, for which it is poison. Leave the
-    // result unknown in that case.
-    if (Shift >= BitWidth)
-      break;
+    // Unlike the IR shifts KnownBits models, a shift by the full element width
+    // is legal here: ushr gives zero, which KnownBits::lshr also returns, and
+    // sshr fills every bit with the sign bit, the same as a shift by
+    // BitWidth - 1.
+    if (MI->getOpcode() == AArch64::G_VASHR)
+      Shift = std::min<uint64_t>(Shift, BitWidth - 1);
     KnownBits SrcKnown(BitWidth);
     Analysis.computeKnownBitsImpl(MI->getOperand(1).getReg(), SrcKnown,
                                   DemandedElts, Depth + 1);
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vashr.mir b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vashr.mir
index dd957d3e24a4c..b991fd2f501ac 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vashr.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vashr.mir
@@ -73,3 +73,39 @@ body: |
     %4:fpr(<8 x i16>) = G_VASHR %3, 4
     $q0 = COPY %4:fpr(<8 x i16>)
 ...
+---
+name: VASHR_by_element_width_negative
+body: |
+  bb.1:
+    liveins: $q0
+  ; CHECK-LABEL: name: @VASHR_by_element_width_negative
+  ; CHECK-NEXT: %0:fpr KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:1000000000000000 SignBits:1 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:fpr KnownBits:1000000000000000 SignBits:1 IsKnownNeverZero:1
+  ; CHECK-NEXT: %3:fpr KnownBits:1??????????????? SignBits:1 IsKnownNeverZero:1
+  ; CHECK-NEXT: %4:fpr KnownBits:1111111111111111 SignBits:16 IsKnownNeverZero:1
+    %0:fpr(<8 x i16>) = COPY $q0
+    %1:_(i16) = G_CONSTANT i16 -32768
+    %2:fpr(<8 x i16>) = G_BUILD_VECTOR %1, %1, %1, %1, %1, %1, %1, %1
+    %3:fpr(<8 x i16>) = G_OR %0, %2
+    %4:fpr(<8 x i16>) = G_VASHR %3, 16
+    $q0 = COPY %4:fpr(<8 x i16>)
+...
+---
+name: VASHR_by_element_width_nonnegative
+body: |
+  bb.1:
+    liveins: $q0
+  ; CHECK-LABEL: name: @VASHR_by_element_width_nonnegative
+  ; CHECK-NEXT: %0:fpr KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:0111111111111111 SignBits:1 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:fpr KnownBits:0111111111111111 SignBits:1 IsKnownNeverZero:1
+  ; CHECK-NEXT: %3:fpr KnownBits:0??????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %4:fpr KnownBits:0000000000000000 SignBits:16 IsKnownNeverZero:0
+    %0:fpr(<8 x i16>) = COPY $q0
+    %1:_(i16) = G_CONSTANT i16 32767
+    %2:fpr(<8 x i16>) = G_BUILD_VECTOR %1, %1, %1, %1, %1, %1, %1, %1
+    %3:fpr(<8 x i16>) = G_AND %0, %2
+    %4:fpr(<8 x i16>) = G_VASHR %3, 16
+    $q0 = COPY %4:fpr(<8 x i16>)
+...
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vlshr.mir b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vlshr.mir
index 9b75766fc9ee3..18f799dabc853 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vlshr.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-vlshr.mir
@@ -26,7 +26,7 @@ body: |
     liveins: $q0
   ; CHECK-LABEL: name: @VLSHR_by_element_width
   ; CHECK-NEXT: %0:fpr KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
-  ; CHECK-NEXT: %1:fpr KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:fpr KnownBits:0000000000000000 SignBits:16 IsKnownNeverZero:0
     %0:fpr(<8 x i16>) = COPY $q0
     %1:fpr(<8 x i16>) = G_VLSHR %0, 16
     $q0 = COPY %1:fpr(<8 x i16>)



More information about the llvm-commits mailing list