[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