[llvm] [InstCombine] Fold xor-lshr sign-mask idiom to ashr (PR #213954)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 4 07:32:58 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: GabrielKim (GabrielKimm)

<details>
<summary>Changes</summary>

Issue #<!-- -->204851 shows an unsigned arithmetic-right-shift idiom that InstCombine currently leaves as a sequence of XOR and shift instructions:

```llvm
((X ^ SignMask) lshr ShiftAmt) ^ SignMask
```

When `SignMask` is `X ashr (BitWidth - 1)`, this is equivalent to `X ashr ShiftAmt`. Teach `visitXor` to perform that fold.

The matcher accepts commuted XOR operands and scalar, fixed-vector, and scalable-vector integer types. The `lshr` must have one use so replacing the outer expression cannot increase the instruction count; the inner XOR may still have other users. The new `ashr` deliberately does not inherit the `exact` flag from either matched shift.

Tests cover commuted operands, exact shifts, unusual integer widths, vector poison, scalable vectors, and negative cases for the source, full-width shift, non-splat vectors, `undef`, and the one-use constraint.

Fixes #<!-- -->204851.

Tested with:

```text
ninja check-llvm-transforms-instcombine
```


---
Full diff: https://github.com/llvm/llvm-project/pull/213954.diff


2 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp (+12) 
- (modified) llvm/test/Transforms/InstCombine/binop-and-shifts.ll (+192) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index b6f4a55c07e8a..05f0bdd8250b3 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -5368,6 +5368,18 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
   if (Instruction *R = foldBinOpShiftWithShift(I))
     return R;
 
+  // ((X ^ SignMask) >>u ShiftAmt) ^ SignMask --> X >>s ShiftAmt
+  // where SignMask = X >>s (BitWidth - 1).
+  Value *Base, *SignMask, *ShiftAmt;
+  if (match(&I, m_c_Xor(m_Value(SignMask),
+                        m_OneUse(
+                            m_LShr(m_c_Xor(m_Deferred(SignMask), m_Value(Base)),
+                                   m_Value(ShiftAmt))))) &&
+      match(SignMask, m_AShr(m_Specific(Base),
+                             m_SpecificIntAllowPoison(
+                                 I.getType()->getScalarSizeInBits() - 1))))
+    return BinaryOperator::CreateAShr(Base, ShiftAmt);
+
   Value *Op0 = I.getOperand(0), *Op1 = I.getOperand(1);
   Value *X, *Y, *M;
 
diff --git a/llvm/test/Transforms/InstCombine/binop-and-shifts.ll b/llvm/test/Transforms/InstCombine/binop-and-shifts.ll
index 40275fd29fde1..3c8c0dd01e810 100644
--- a/llvm/test/Transforms/InstCombine/binop-and-shifts.ll
+++ b/llvm/test/Transforms/InstCombine/binop-and-shifts.ll
@@ -1,6 +1,8 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
 ; RUN: opt < %s -passes=instcombine -S | FileCheck %s
 
+declare void @use(i8)
+
 define i8 @shl_and_and(i8 %x, i8 %y) {
 ; CHECK-LABEL: @shl_and_and(
 ; CHECK-NEXT:    [[TMP1:%.*]] = and i8 [[Y:%.*]], [[X:%.*]]
@@ -951,6 +953,196 @@ define <4 x i8> @xor_ashr_not_vec_poison_2(<4 x i8> %x, <4 x i8> %y, <4 x i8> %s
   ret <4 x i8> %xor
 }
 
+define i8 @xor_lshr_signmask(i8 %x, i8 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask(
+; CHECK-NEXT:    [[R:%.*]] = ashr i8 [[X:%.*]], [[SHAMT:%.*]]
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %signmask = ashr i8 %x, 7
+  %flipped = xor i8 %x, %signmask
+  %shifted = lshr i8 %flipped, %shamt
+  %r = xor i8 %shifted, %signmask
+  ret i8 %r
+}
+
+define i8 @xor_lshr_signmask_commuted(i8 %x, i8 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_commuted(
+; CHECK-NEXT:    [[R:%.*]] = ashr i8 [[X:%.*]], [[SHAMT:%.*]]
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %signmask = ashr i8 %x, 7
+  %flipped = xor i8 %signmask, %x
+  %shifted = lshr i8 %flipped, %shamt
+  %r = xor i8 %signmask, %shifted
+  ret i8 %r
+}
+
+define i8 @xor_lshr_signmask_exact(i8 %x, i8 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_exact(
+; CHECK-NEXT:    [[R:%.*]] = ashr i8 [[X:%.*]], [[SHAMT:%.*]]
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %signmask = ashr i8 %x, 7
+  %flipped = xor i8 %x, %signmask
+  %shifted = lshr exact i8 %flipped, %shamt
+  %r = xor i8 %shifted, %signmask
+  ret i8 %r
+}
+
+define i1 @xor_lshr_signmask_i1(i1 %x, i1 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_i1(
+; CHECK-NEXT:    ret i1 [[X:%.*]]
+;
+  %signmask = ashr i1 %x, 0
+  %flipped = xor i1 %x, %signmask
+  %shifted = lshr i1 %flipped, %shamt
+  %r = xor i1 %shifted, %signmask
+  ret i1 %r
+}
+
+define i13 @xor_lshr_signmask_i13(i13 %x, i13 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_i13(
+; CHECK-NEXT:    [[R:%.*]] = ashr i13 [[X:%.*]], [[SHAMT:%.*]]
+; CHECK-NEXT:    ret i13 [[R]]
+;
+  %signmask = ashr i13 %x, 12
+  %flipped = xor i13 %x, %signmask
+  %shifted = lshr i13 %flipped, %shamt
+  %r = xor i13 %shifted, %signmask
+  ret i13 %r
+}
+
+define i8 @xor_lshr_signmask_signmask_exact(i8 %x, i8 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_signmask_exact(
+; CHECK-NEXT:    [[R:%.*]] = ashr i8 [[X:%.*]], [[SHAMT:%.*]]
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %signmask = ashr exact i8 %x, 7
+  %flipped = xor i8 %x, %signmask
+  %shifted = lshr i8 %flipped, %shamt
+  %r = xor i8 %shifted, %signmask
+  ret i8 %r
+}
+
+define { i8, i8 } @xor_lshr_signmask_inner_multiuse(i8 %x, i8 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_inner_multiuse(
+; CHECK-NEXT:    [[SIGNMASK:%.*]] = ashr i8 [[X:%.*]], 7
+; CHECK-NEXT:    [[FLIPPED:%.*]] = xor i8 [[X]], [[SIGNMASK]]
+; CHECK-NEXT:    [[R:%.*]] = ashr i8 [[X]], [[SHAMT:%.*]]
+; CHECK-NEXT:    [[R0:%.*]] = insertvalue { i8, i8 } poison, i8 [[R]], 0
+; CHECK-NEXT:    [[R1:%.*]] = insertvalue { i8, i8 } [[R0]], i8 [[FLIPPED]], 1
+; CHECK-NEXT:    ret { i8, i8 } [[R1]]
+;
+  %signmask = ashr i8 %x, 7
+  %flipped = xor i8 %x, %signmask
+  %shifted = lshr i8 %flipped, %shamt
+  %r = xor i8 %shifted, %signmask
+  %r0 = insertvalue { i8, i8 } poison, i8 %r, 0
+  %r1 = insertvalue { i8, i8 } %r0, i8 %flipped, 1
+  ret { i8, i8 } %r1
+}
+
+define <4 x i8> @xor_lshr_signmask_vec_poison(<4 x i8> %x, <4 x i8> %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_vec_poison(
+; CHECK-NEXT:    [[R:%.*]] = ashr <4 x i8> [[X:%.*]], [[SHAMT:%.*]]
+; CHECK-NEXT:    ret <4 x i8> [[R]]
+;
+  %signmask = ashr <4 x i8> %x, <i8 7, i8 poison, i8 7, i8 poison>
+  %flipped = xor <4 x i8> %x, %signmask
+  %shifted = lshr <4 x i8> %flipped, %shamt
+  %r = xor <4 x i8> %shifted, %signmask
+  ret <4 x i8> %r
+}
+
+define <4 x i8> @xor_lshr_signmask_vec_undef(<4 x i8> %x, <4 x i8> %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_vec_undef(
+; CHECK-NEXT:    [[SIGNMASK:%.*]] = ashr <4 x i8> [[X:%.*]], <i8 7, i8 undef, i8 7, i8 undef>
+; CHECK-NEXT:    [[FLIPPED:%.*]] = xor <4 x i8> [[X]], [[SIGNMASK]]
+; CHECK-NEXT:    [[SHIFTED:%.*]] = lshr <4 x i8> [[FLIPPED]], [[SHAMT:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = xor <4 x i8> [[SHIFTED]], [[SIGNMASK]]
+; CHECK-NEXT:    ret <4 x i8> [[R]]
+;
+  %signmask = ashr <4 x i8> %x, <i8 7, i8 undef, i8 7, i8 undef>
+  %flipped = xor <4 x i8> %x, %signmask
+  %shifted = lshr <4 x i8> %flipped, %shamt
+  %r = xor <4 x i8> %shifted, %signmask
+  ret <4 x i8> %r
+}
+
+define <vscale x 4 x i8> @xor_lshr_signmask_scalable(<vscale x 4 x i8> %x, <vscale x 4 x i8> %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_scalable(
+; CHECK-NEXT:    [[R:%.*]] = ashr <vscale x 4 x i8> [[X:%.*]], [[SHAMT:%.*]]
+; CHECK-NEXT:    ret <vscale x 4 x i8> [[R]]
+;
+  %signmask = ashr <vscale x 4 x i8> %x, splat (i8 7)
+  %flipped = xor <vscale x 4 x i8> %x, %signmask
+  %shifted = lshr <vscale x 4 x i8> %flipped, %shamt
+  %r = xor <vscale x 4 x i8> %shifted, %signmask
+  ret <vscale x 4 x i8> %r
+}
+
+define i8 @xor_lshr_signmask_wrong_source(i8 %x, i8 %y, i8 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_wrong_source(
+; CHECK-NEXT:    [[SIGNMASK:%.*]] = ashr i8 [[Y:%.*]], 7
+; CHECK-NEXT:    [[FLIPPED:%.*]] = xor i8 [[X:%.*]], [[SIGNMASK]]
+; CHECK-NEXT:    [[SHIFTED:%.*]] = lshr i8 [[FLIPPED]], [[SHAMT:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = xor i8 [[SHIFTED]], [[SIGNMASK]]
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %signmask = ashr i8 %y, 7
+  %flipped = xor i8 %x, %signmask
+  %shifted = lshr i8 %flipped, %shamt
+  %r = xor i8 %shifted, %signmask
+  ret i8 %r
+}
+
+define i8 @xor_lshr_signmask_wrong_full_shift(i8 %x, i8 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_wrong_full_shift(
+; CHECK-NEXT:    [[SIGNMASK:%.*]] = ashr i8 [[X:%.*]], 6
+; CHECK-NEXT:    [[FLIPPED:%.*]] = xor i8 [[X]], [[SIGNMASK]]
+; CHECK-NEXT:    [[SHIFTED:%.*]] = lshr i8 [[FLIPPED]], [[SHAMT:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = xor i8 [[SHIFTED]], [[SIGNMASK]]
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %signmask = ashr i8 %x, 6
+  %flipped = xor i8 %x, %signmask
+  %shifted = lshr i8 %flipped, %shamt
+  %r = xor i8 %shifted, %signmask
+  ret i8 %r
+}
+
+define i8 @xor_lshr_signmask_lshr_multiuse(i8 %x, i8 %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_lshr_multiuse(
+; CHECK-NEXT:    [[SIGNMASK:%.*]] = ashr i8 [[X:%.*]], 7
+; CHECK-NEXT:    [[FLIPPED:%.*]] = xor i8 [[X]], [[SIGNMASK]]
+; CHECK-NEXT:    [[SHIFTED:%.*]] = lshr i8 [[FLIPPED]], [[SHAMT:%.*]]
+; CHECK-NEXT:    call void @use(i8 [[SHIFTED]])
+; CHECK-NEXT:    [[R:%.*]] = xor i8 [[SHIFTED]], [[SIGNMASK]]
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %signmask = ashr i8 %x, 7
+  %flipped = xor i8 %x, %signmask
+  %shifted = lshr i8 %flipped, %shamt
+  call void @use(i8 %shifted)
+  %r = xor i8 %shifted, %signmask
+  ret i8 %r
+}
+
+define <4 x i8> @xor_lshr_signmask_vec_nonsplat(<4 x i8> %x, <4 x i8> %shamt) {
+; CHECK-LABEL: @xor_lshr_signmask_vec_nonsplat(
+; CHECK-NEXT:    [[SIGNMASK:%.*]] = ashr <4 x i8> [[X:%.*]], <i8 7, i8 6, i8 7, i8 6>
+; CHECK-NEXT:    [[FLIPPED:%.*]] = xor <4 x i8> [[X]], [[SIGNMASK]]
+; CHECK-NEXT:    [[SHIFTED:%.*]] = lshr <4 x i8> [[FLIPPED]], [[SHAMT:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = xor <4 x i8> [[SHIFTED]], [[SIGNMASK]]
+; CHECK-NEXT:    ret <4 x i8> [[R]]
+;
+  %signmask = ashr <4 x i8> %x, <i8 7, i8 6, i8 7, i8 6>
+  %flipped = xor <4 x i8> %x, %signmask
+  %shifted = lshr <4 x i8> %flipped, %shamt
+  %r = xor <4 x i8> %shifted, %signmask
+  ret <4 x i8> %r
+}
+
 ; Negative test: outer binop has multiple users
 
 define i8 @shl_add_add_multiuse_binop(i8 %x, i8 %y0, i8 %y1) {

``````````

</details>


https://github.com/llvm/llvm-project/pull/213954


More information about the llvm-commits mailing list