[llvm] [InstSimplify] Fold fshl of a sign mask (PR #219824)

via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 30 09:27:47 PDT 2026


https://github.com/GabrielKimm created https://github.com/llvm/llvm-project/pull/219824

## Summary

Issue #102875 shows a funnel-shift pattern produced by a portable arithmetic
right shift of a multiword signed integer:

```llvm
%sign = ashr i64 %x, 63
%result = call i64 @llvm.fshl.i64(i64 %sign, i64 %x, i64 1)
```

`%sign` is either zero or all ones, and the bit shifted in from `%x` is the
same sign bit. Therefore, `fshl(%sign, %x, 1)` is equal to `%sign`.

Teach `simplifyIntrinsic()` to return the existing sign-mask value when the
first operand is `ashr X, BitWidth - 1`, the second operand is the same `X`,
and the normalized funnel-shift amount is one. Returning the existing value
preserves `ashr exact` poison behavior and does not require a one-use check.

The existing constant matchers also make the fold apply lane-wise to defined
fixed and scalable vector splats. Defined non-splat, mixed-lane, or poison
shift constants do not match.

## Correctness

For a `BitWidth`-bit value, `S = ashr(X, BitWidth - 1)` is either zero or all
ones. `fshl(S, X, 1)` shifts `S` left and inserts the sign bit of `X`, which
reconstructs `S` in both cases.

Alive2 proof from the issue:
https://alive2.llvm.org/ce/z/dew45A

The identity was also exhaustively evaluated for all values at widths i1
through i16, including shift amounts congruent to one modulo the bit width
(393,210 checks).

## Tests

Tests cover the motivating i64 multi-use form, an unusual i13 width with a
modulo-equivalent shift, `ashr exact`, fixed and scalable vector splats, and
negative cases for every matcher precondition, including defined non-splat and
poison vector shift amounts.

Tested with:

```text
cmake --build build --target opt -j 8
build/bin/llvm-lit -sv llvm/test/Transforms/InstSimplify/call.ll
  1 passed
cmake --build build --target check-llvm-transforms-instsimplify -j 8
  279 passed, 13 unsupported, 0 failed
build/bin/opt -passes=instsimplify -verify-each  # direct issue IR
build/bin/opt -passes=instcombine -verify-each  # direct issue IR
```

Fixes #102875.

AI assistance: OpenAI Codex assisted with analysis, implementation, test generation, and drafting this pull request. I reviewed and understand all resulting code and text and take full responsibility for this contribution.


>From e4b3dc883d02a540d70f7622b4388fbbf6cfd975 Mon Sep 17 00:00:00 2001
From: GabrielKim <0xGabriel.kim at gmail.com>
Date: Mon, 31 Aug 2026 01:25:13 +0900
Subject: [PATCH 1/2] [InstSimplify] Add tests for fshl sign-mask fold

---
 llvm/test/Transforms/InstSimplify/call.ll | 136 ++++++++++++++++++++++
 1 file changed, 136 insertions(+)

diff --git a/llvm/test/Transforms/InstSimplify/call.ll b/llvm/test/Transforms/InstSimplify/call.ll
index 300c27c98ef99..7872d4f3f91f8 100644
--- a/llvm/test/Transforms/InstSimplify/call.ll
+++ b/llvm/test/Transforms/InstSimplify/call.ll
@@ -659,6 +659,142 @@ define i32 @fshl_not_identity_wrong_sum(i32 %x) {
   ret i32 %r
 }
 
+define { i64, i64 } @fshl_sign_bit_multi_use(i64 %x) {
+; CHECK-LABEL: @fshl_sign_bit_multi_use(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr i64 [[X:%.*]], 63
+; CHECK-NEXT:    [[R:%.*]] = call i64 @llvm.fshl.i64(i64 [[SIGN]], i64 [[X]], i64 1)
+; CHECK-NEXT:    [[R0:%.*]] = insertvalue { i64, i64 } poison, i64 [[R]], 0
+; CHECK-NEXT:    [[R1:%.*]] = insertvalue { i64, i64 } [[R0]], i64 [[SIGN]], 1
+; CHECK-NEXT:    ret { i64, i64 } [[R1]]
+;
+  %sign = ashr i64 %x, 63
+  %r = call i64 @llvm.fshl.i64(i64 %sign, i64 %x, i64 1)
+  %r0 = insertvalue { i64, i64 } poison, i64 %r, 0
+  %r1 = insertvalue { i64, i64 } %r0, i64 %sign, 1
+  ret { i64, i64 } %r1
+}
+
+define i13 @fshl_sign_bit_modulo(i13 %x) {
+; CHECK-LABEL: @fshl_sign_bit_modulo(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr i13 [[X:%.*]], 12
+; CHECK-NEXT:    [[R:%.*]] = call i13 @llvm.fshl.i13(i13 [[SIGN]], i13 [[X]], i13 14)
+; CHECK-NEXT:    ret i13 [[R]]
+;
+  %sign = ashr i13 %x, 12
+  %r = call i13 @llvm.fshl.i13(i13 %sign, i13 %x, i13 14)
+  ret i13 %r
+}
+
+define i8 @fshl_sign_bit_exact(i8 %x) {
+; CHECK-LABEL: @fshl_sign_bit_exact(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr exact i8 [[X:%.*]], 7
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.fshl.i8(i8 [[SIGN]], i8 [[X]], i8 1)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sign = ashr exact i8 %x, 7
+  %r = call i8 @llvm.fshl.i8(i8 %sign, i8 %x, i8 1)
+  ret i8 %r
+}
+
+define <2 x i7> @fshl_sign_bit_splat(<2 x i7> %x) {
+; CHECK-LABEL: @fshl_sign_bit_splat(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr <2 x i7> [[X:%.*]], splat (i7 6)
+; CHECK-NEXT:    [[R:%.*]] = call <2 x i7> @llvm.fshl.v2i7(<2 x i7> [[SIGN]], <2 x i7> [[X]], <2 x i7> splat (i7 1))
+; CHECK-NEXT:    ret <2 x i7> [[R]]
+;
+  %sign = ashr <2 x i7> %x, <i7 6, i7 6>
+  %r = call <2 x i7> @llvm.fshl.v2i7(<2 x i7> %sign, <2 x i7> %x, <2 x i7> <i7 1, i7 1>)
+  ret <2 x i7> %r
+}
+
+define <vscale x 2 x i7> @fshl_sign_bit_scalable_splat(<vscale x 2 x i7> %x) {
+; CHECK-LABEL: @fshl_sign_bit_scalable_splat(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr <vscale x 2 x i7> [[X:%.*]], splat (i7 6)
+; CHECK-NEXT:    [[R:%.*]] = call <vscale x 2 x i7> @llvm.fshl.nxv2i7(<vscale x 2 x i7> [[SIGN]], <vscale x 2 x i7> [[X]], <vscale x 2 x i7> splat (i7 1))
+; CHECK-NEXT:    ret <vscale x 2 x i7> [[R]]
+;
+  %sign = ashr <vscale x 2 x i7> %x, splat (i7 6)
+  %r = call <vscale x 2 x i7> @llvm.fshl.nxv2i7(<vscale x 2 x i7> %sign, <vscale x 2 x i7> %x, <vscale x 2 x i7> splat (i7 1))
+  ret <vscale x 2 x i7> %r
+}
+
+define i8 @fshl_sign_bit_wrong_source(i8 %x, i8 %y) {
+; CHECK-LABEL: @fshl_sign_bit_wrong_source(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr i8 [[X:%.*]], 7
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.fshl.i8(i8 [[SIGN]], i8 [[Y:%.*]], i8 1)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sign = ashr i8 %x, 7
+  %r = call i8 @llvm.fshl.i8(i8 %sign, i8 %y, i8 1)
+  ret i8 %r
+}
+
+define i8 @fshl_sign_bit_wrong_ashr_amount(i8 %x) {
+; CHECK-LABEL: @fshl_sign_bit_wrong_ashr_amount(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr i8 [[X:%.*]], 6
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.fshl.i8(i8 [[SIGN]], i8 [[X]], i8 1)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sign = ashr i8 %x, 6
+  %r = call i8 @llvm.fshl.i8(i8 %sign, i8 %x, i8 1)
+  ret i8 %r
+}
+
+define i8 @fshl_sign_bit_wrong_fshl_amount(i8 %x) {
+; CHECK-LABEL: @fshl_sign_bit_wrong_fshl_amount(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr i8 [[X:%.*]], 7
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.fshl.i8(i8 [[SIGN]], i8 [[X]], i8 2)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sign = ashr i8 %x, 7
+  %r = call i8 @llvm.fshl.i8(i8 %sign, i8 %x, i8 2)
+  ret i8 %r
+}
+
+define i8 @fshl_lshr_high_bit(i8 %x) {
+; CHECK-LABEL: @fshl_lshr_high_bit(
+; CHECK-NEXT:    [[HIGH:%.*]] = lshr i8 [[X:%.*]], 7
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.fshl.i8(i8 [[HIGH]], i8 [[X]], i8 1)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %high = lshr i8 %x, 7
+  %r = call i8 @llvm.fshl.i8(i8 %high, i8 %x, i8 1)
+  ret i8 %r
+}
+
+define i9 @fshr_sign_bit(i9 %x) {
+; CHECK-LABEL: @fshr_sign_bit(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr i9 [[X:%.*]], 8
+; CHECK-NEXT:    [[R:%.*]] = call i9 @llvm.fshr.i9(i9 [[SIGN]], i9 [[X]], i9 1)
+; CHECK-NEXT:    ret i9 [[R]]
+;
+  %sign = ashr i9 %x, 8
+  %r = call i9 @llvm.fshr.i9(i9 %sign, i9 %x, i9 1)
+  ret i9 %r
+}
+
+define <2 x i7> @fshl_sign_bit_non_splat_shift_amount(<2 x i7> %x) {
+; CHECK-LABEL: @fshl_sign_bit_non_splat_shift_amount(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr <2 x i7> [[X:%.*]], splat (i7 6)
+; CHECK-NEXT:    [[R:%.*]] = call <2 x i7> @llvm.fshl.v2i7(<2 x i7> [[SIGN]], <2 x i7> [[X]], <2 x i7> <i7 1, i7 2>)
+; CHECK-NEXT:    ret <2 x i7> [[R]]
+;
+  %sign = ashr <2 x i7> %x, <i7 6, i7 6>
+  %r = call <2 x i7> @llvm.fshl.v2i7(<2 x i7> %sign, <2 x i7> %x, <2 x i7> <i7 1, i7 2>)
+  ret <2 x i7> %r
+}
+
+define <2 x i7> @fshl_sign_bit_poison_shift_amount(<2 x i7> %x) {
+; CHECK-LABEL: @fshl_sign_bit_poison_shift_amount(
+; CHECK-NEXT:    [[SIGN:%.*]] = ashr <2 x i7> [[X:%.*]], splat (i7 6)
+; CHECK-NEXT:    [[R:%.*]] = call <2 x i7> @llvm.fshl.v2i7(<2 x i7> [[SIGN]], <2 x i7> [[X]], <2 x i7> <i7 1, i7 poison>)
+; CHECK-NEXT:    ret <2 x i7> [[R]]
+;
+  %sign = ashr <2 x i7> %x, <i7 6, i7 6>
+  %r = call <2 x i7> @llvm.fshl.v2i7(<2 x i7> %sign, <2 x i7> %x, <2 x i7> <i7 1, i7 poison>)
+  ret <2 x i7> %r
+}
+
 ; If y is poison, eliminating the guard is not safe.
 
 define i8 @fshl_zero_shift_guard(i8 %x, i8 %y, i8 %sh) {

>From bae9f5e7465d4b0d6acb9975e8b13f89d1f73215 Mon Sep 17 00:00:00 2001
From: GabrielKim <0xGabriel.kim at gmail.com>
Date: Mon, 31 Aug 2026 01:26:00 +0900
Subject: [PATCH 2/2] [InstSimplify] Fold fshl of a sign mask

---
 llvm/lib/Analysis/InstructionSimplify.cpp |  5 +++++
 llvm/test/Transforms/InstSimplify/call.ll | 15 +++++----------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index c76f4d2e9f327..0023e1d6f98a8 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -7413,6 +7413,11 @@ Value *llvm::simplifyIntrinsic(Intrinsic::ID IID, Type *ReturnType,
       if (ShAmt.isZero())
         return Args[IID == Intrinsic::fshl ? 0 : 1];
 
+      // fshl(ashr(X, BW - 1), X, 1) -> ashr(X, BW - 1)
+      if (IID == Intrinsic::fshl && ShAmt.isOne() &&
+          match(Op0, m_AShr(m_Specific(Op1), m_SpecificInt(BitWidth - 1))))
+        return Op0;
+
       // fshl (lshr X, C1), (shl X, C2), C1 -> X when C1 + C2 == BW
       // fshr (lshr X, C1), (shl X, C2), C2 -> X when C1 + C2 == BW
       const APInt *C1, *C2;
diff --git a/llvm/test/Transforms/InstSimplify/call.ll b/llvm/test/Transforms/InstSimplify/call.ll
index 7872d4f3f91f8..c7b36e945318a 100644
--- a/llvm/test/Transforms/InstSimplify/call.ll
+++ b/llvm/test/Transforms/InstSimplify/call.ll
@@ -662,8 +662,7 @@ define i32 @fshl_not_identity_wrong_sum(i32 %x) {
 define { i64, i64 } @fshl_sign_bit_multi_use(i64 %x) {
 ; CHECK-LABEL: @fshl_sign_bit_multi_use(
 ; CHECK-NEXT:    [[SIGN:%.*]] = ashr i64 [[X:%.*]], 63
-; CHECK-NEXT:    [[R:%.*]] = call i64 @llvm.fshl.i64(i64 [[SIGN]], i64 [[X]], i64 1)
-; CHECK-NEXT:    [[R0:%.*]] = insertvalue { i64, i64 } poison, i64 [[R]], 0
+; CHECK-NEXT:    [[R0:%.*]] = insertvalue { i64, i64 } poison, i64 [[SIGN]], 0
 ; CHECK-NEXT:    [[R1:%.*]] = insertvalue { i64, i64 } [[R0]], i64 [[SIGN]], 1
 ; CHECK-NEXT:    ret { i64, i64 } [[R1]]
 ;
@@ -677,8 +676,7 @@ define { i64, i64 } @fshl_sign_bit_multi_use(i64 %x) {
 define i13 @fshl_sign_bit_modulo(i13 %x) {
 ; CHECK-LABEL: @fshl_sign_bit_modulo(
 ; CHECK-NEXT:    [[SIGN:%.*]] = ashr i13 [[X:%.*]], 12
-; CHECK-NEXT:    [[R:%.*]] = call i13 @llvm.fshl.i13(i13 [[SIGN]], i13 [[X]], i13 14)
-; CHECK-NEXT:    ret i13 [[R]]
+; CHECK-NEXT:    ret i13 [[SIGN]]
 ;
   %sign = ashr i13 %x, 12
   %r = call i13 @llvm.fshl.i13(i13 %sign, i13 %x, i13 14)
@@ -688,8 +686,7 @@ define i13 @fshl_sign_bit_modulo(i13 %x) {
 define i8 @fshl_sign_bit_exact(i8 %x) {
 ; CHECK-LABEL: @fshl_sign_bit_exact(
 ; CHECK-NEXT:    [[SIGN:%.*]] = ashr exact i8 [[X:%.*]], 7
-; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.fshl.i8(i8 [[SIGN]], i8 [[X]], i8 1)
-; CHECK-NEXT:    ret i8 [[R]]
+; CHECK-NEXT:    ret i8 [[SIGN]]
 ;
   %sign = ashr exact i8 %x, 7
   %r = call i8 @llvm.fshl.i8(i8 %sign, i8 %x, i8 1)
@@ -699,8 +696,7 @@ define i8 @fshl_sign_bit_exact(i8 %x) {
 define <2 x i7> @fshl_sign_bit_splat(<2 x i7> %x) {
 ; CHECK-LABEL: @fshl_sign_bit_splat(
 ; CHECK-NEXT:    [[SIGN:%.*]] = ashr <2 x i7> [[X:%.*]], splat (i7 6)
-; CHECK-NEXT:    [[R:%.*]] = call <2 x i7> @llvm.fshl.v2i7(<2 x i7> [[SIGN]], <2 x i7> [[X]], <2 x i7> splat (i7 1))
-; CHECK-NEXT:    ret <2 x i7> [[R]]
+; CHECK-NEXT:    ret <2 x i7> [[SIGN]]
 ;
   %sign = ashr <2 x i7> %x, <i7 6, i7 6>
   %r = call <2 x i7> @llvm.fshl.v2i7(<2 x i7> %sign, <2 x i7> %x, <2 x i7> <i7 1, i7 1>)
@@ -710,8 +706,7 @@ define <2 x i7> @fshl_sign_bit_splat(<2 x i7> %x) {
 define <vscale x 2 x i7> @fshl_sign_bit_scalable_splat(<vscale x 2 x i7> %x) {
 ; CHECK-LABEL: @fshl_sign_bit_scalable_splat(
 ; CHECK-NEXT:    [[SIGN:%.*]] = ashr <vscale x 2 x i7> [[X:%.*]], splat (i7 6)
-; CHECK-NEXT:    [[R:%.*]] = call <vscale x 2 x i7> @llvm.fshl.nxv2i7(<vscale x 2 x i7> [[SIGN]], <vscale x 2 x i7> [[X]], <vscale x 2 x i7> splat (i7 1))
-; CHECK-NEXT:    ret <vscale x 2 x i7> [[R]]
+; CHECK-NEXT:    ret <vscale x 2 x i7> [[SIGN]]
 ;
   %sign = ashr <vscale x 2 x i7> %x, splat (i7 6)
   %r = call <vscale x 2 x i7> @llvm.fshl.nxv2i7(<vscale x 2 x i7> %sign, <vscale x 2 x i7> %x, <vscale x 2 x i7> splat (i7 1))



More information about the llvm-commits mailing list