[llvm] [AArch64] Add sext(shl x, imm)/sext(sra x, imm) selection patterns (PR #226065)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 24 01:16:35 PDT 2026
https://github.com/KRM7 created https://github.com/llvm/llvm-project/pull/226065
Currently there are no selection patterns for code where the result of a constant shift is sign-extended, even though these can be selected to a single sbfiz/sbfx instruction. For example, at the moment, 2 instructions are generated for the following C function:
```C
int64_t shl_ext(int8_t x) { return x * 2; }
```
>From b98f960ca680c62f725816ec87ed4ee09f3d971e Mon Sep 17 00:00:00 2001
From: Krisztian Rugasi <krisztian.rugasi at arm.com>
Date: Thu, 24 Sep 2026 07:45:45 +0000
Subject: [PATCH] [AArch64] Add sext(shl x, imm)/sext(sra x, imm) selection
patterns
Currently there are no selection patterns for code where the result
of a constant shift is sign-extended, even though these can be
selected to a single sbfiz/sbfx instruction. For example, at the
moment, 2 instructions are generated for the following C function:
int64_t shl_ext(int8_t x) { return x * 2; }
---
.../Target/AArch64/AArch64ISelDAGToDAG.cpp | 30 ----
llvm/lib/Target/AArch64/AArch64InstrInfo.td | 27 ++++
.../CodeGen/AArch64/arm64-shifted-sext.ll | 140 ++++++++++++++++++
llvm/test/CodeGen/AArch64/lslfast.ll | 4 +-
4 files changed, 169 insertions(+), 32 deletions(-)
diff --git a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
index feb8f46583794..ba8d2eb61c2b0 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
@@ -441,7 +441,6 @@ class AArch64DAGToDAGISel : public SelectionDAGISel {
unsigned Scale);
bool tryBitfieldExtractOp(SDNode *N);
- bool tryBitfieldExtractOpFromSExt(SDNode *N);
bool tryBitfieldInsertOp(SDNode *N);
bool tryBitfieldInsertInZeroOp(SDNode *N);
bool tryShiftAmountMod(SDNode *N);
@@ -3079,30 +3078,6 @@ static bool isBitfieldExtractOpFromShr(SDNode *N, unsigned &Opc, SDValue &Opd0,
return true;
}
-bool AArch64DAGToDAGISel::tryBitfieldExtractOpFromSExt(SDNode *N) {
- assert(N->getOpcode() == ISD::SIGN_EXTEND);
-
- EVT VT = N->getValueType(0);
- EVT NarrowVT = N->getOperand(0)->getValueType(0);
- if (VT != MVT::i64 || NarrowVT != MVT::i32)
- return false;
-
- uint64_t ShiftImm;
- SDValue Op = N->getOperand(0);
- if (!isOpcWithIntImmediate(Op.getNode(), ISD::SRA, ShiftImm))
- return false;
-
- SDLoc dl(N);
- // Extend the incoming operand of the shift to 64-bits.
- SDValue Opd0 = Widen(CurDAG, Op.getOperand(0));
- unsigned Immr = ShiftImm;
- unsigned Imms = NarrowVT.getSizeInBits() - 1;
- SDValue Ops[] = {Opd0, CurDAG->getTargetConstant(Immr, dl, VT),
- CurDAG->getTargetConstant(Imms, dl, VT)};
- CurDAG->SelectNodeTo(N, AArch64::SBFMXri, VT, Ops);
- return true;
-}
-
static bool isBitfieldExtractOp(SelectionDAG *CurDAG, SDNode *N, unsigned &Opc,
SDValue &Opd0, unsigned &Immr, unsigned &Imms,
unsigned NumberOfIgnoredLowBits = 0,
@@ -5213,11 +5188,6 @@ void AArch64DAGToDAGISel::Select(SDNode *Node) {
return;
break;
- case ISD::SIGN_EXTEND:
- if (tryBitfieldExtractOpFromSExt(Node))
- return;
- break;
-
case ISD::OR:
if (tryBitfieldInsertOp(Node))
return;
diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index 4a65c2ebb2906..ed11428b44dd2 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -10914,6 +10914,21 @@ def : Pat<(shl (i64 (zext GPR32:$Rn)), (i64 imm0_63:$imm)),
(i64 (i64shift_a imm0_63:$imm)),
(i64 (i64shift_sext_i32 imm0_63:$imm)))>;
+def : Pat<(i64 (sext (shl GPR32:$Rn, (i64 imm0_31:$imm)))),
+ (SBFMXri (INSERT_SUBREG (i64 (IMPLICIT_DEF)), GPR32:$Rn, sub_32),
+ (i64 (i64shift_a imm0_31:$imm)),
+ (i64 (i32shift_b imm0_31:$imm)))>;
+
+def : Pat<(i64 (sext (shl (sext_inreg GPR32:$Rn, i8), (i64 imm0_31:$imm)))),
+ (SBFMXri (INSERT_SUBREG (i64 (IMPLICIT_DEF)), GPR32:$Rn, sub_32),
+ (i64 (i64shift_a imm0_31:$imm)),
+ (i64 (i32shift_sext_i8 imm0_31:$imm)))>;
+
+def : Pat<(i64 (sext (shl (sext_inreg GPR32:$Rn, i16), (i64 imm0_31:$imm)))),
+ (SBFMXri (INSERT_SUBREG (i64 (IMPLICIT_DEF)), GPR32:$Rn, sub_32),
+ (i64 (i64shift_a imm0_31:$imm)),
+ (i64 (i32shift_sext_i16 imm0_31:$imm)))>;
+
// sra patterns have an AddedComplexity of 10, so make sure we have a higher
// AddedComplexity for the following patterns since we want to match sext + sra
// patterns before we attempt to match a single sra node.
@@ -10936,6 +10951,18 @@ def : Pat<(sra (i64 (sext GPR32:$Rn)), (i64 imm0_31:$imm)),
(i64 imm0_31:$imm), 31)>;
} // AddedComplexity = 20
+def : Pat<(i64 (sext (sra GPR32:$Rn, (i64 imm0_31:$imm)))),
+ (SBFMXri (INSERT_SUBREG (i64 (IMPLICIT_DEF)), GPR32:$Rn, sub_32),
+ (i64 imm0_31:$imm), 31)>;
+
+def : Pat<(i64 (sext (sra (sext_inreg GPR32:$Rn, i8), (i64 imm0_31:$imm)))),
+ (SBFMXri (INSERT_SUBREG (i64 (IMPLICIT_DEF)), GPR32:$Rn, sub_32),
+ (i64 imm0_31:$imm), 7)>;
+
+def : Pat<(i64 (sext (sra (sext_inreg GPR32:$Rn, i16), (i64 imm0_31:$imm)))),
+ (SBFMXri (INSERT_SUBREG (i64 (IMPLICIT_DEF)), GPR32:$Rn, sub_32),
+ (i64 imm0_31:$imm), 15)>;
+
// To truncate, we can simply extract from a subregister.
def : Pat<(i32 (trunc GPR64sp:$src)),
(i32 (EXTRACT_SUBREG GPR64sp:$src, sub_32))>;
diff --git a/llvm/test/CodeGen/AArch64/arm64-shifted-sext.ll b/llvm/test/CodeGen/AArch64/arm64-shifted-sext.ll
index da6499b7daa82..ef8243960c534 100644
--- a/llvm/test/CodeGen/AArch64/arm64-shifted-sext.ll
+++ b/llvm/test/CodeGen/AArch64/arm64-shifted-sext.ll
@@ -339,3 +339,143 @@ define i64 @sign_extend_inreg_isdef32(i64) {
%6 = zext i32 %5 to i64
ret i64 %6
}
+
+define i64 @shl1_sext_i32(i32 %val) nounwind {
+; CHECK-LABEL: shl1_sext_i32:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfiz x0, x0, #1, #31
+; CHECK-NEXT: ret
+ %1 = shl i32 %val, 1
+ %2 = sext i32 %1 to i64
+ ret i64 %2
+}
+
+define i64 @shl31_sext_i32(i32 %val) nounwind {
+; CHECK-LABEL: shl31_sext_i32:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfiz x0, x0, #31, #1
+; CHECK-NEXT: ret
+ %1 = shl i32 %val, 31
+ %2 = sext i32 %1 to i64
+ ret i64 %2
+}
+
+define i64 @sext_shl1_sext_i8(i8 %val) nounwind {
+; CHECK-LABEL: sext_shl1_sext_i8:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfiz x0, x0, #1, #8
+; CHECK-NEXT: ret
+ %1 = sext i8 %val to i32
+ %2 = shl i32 %1, 1
+ %3 = sext i32 %2 to i64
+ ret i64 %3
+}
+
+define i64 @sext_shl31_sext_i8(i8 %val) nounwind {
+; CHECK-LABEL: sext_shl31_sext_i8:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfiz x0, x0, #31, #1
+; CHECK-NEXT: ret
+ %1 = sext i8 %val to i32
+ %2 = shl i32 %1, 31
+ %3 = sext i32 %2 to i64
+ ret i64 %3
+}
+
+define i64 @sext_shl1_sext_i16(i16 %val) nounwind {
+; CHECK-LABEL: sext_shl1_sext_i16:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfiz x0, x0, #1, #16
+; CHECK-NEXT: ret
+ %1 = sext i16 %val to i32
+ %2 = shl i32 %1, 1
+ %3 = sext i32 %2 to i64
+ ret i64 %3
+}
+
+define i64 @sext_shl31_sext_i16(i16 %val) nounwind {
+; CHECK-LABEL: sext_shl31_sext_i16:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfiz x0, x0, #31, #1
+; CHECK-NEXT: ret
+ %1 = sext i16 %val to i32
+ %2 = shl i32 %1, 31
+ %3 = sext i32 %2 to i64
+ ret i64 %3
+}
+
+define i64 @ashr1_sext_i32(i32 %val) nounwind {
+; CHECK-LABEL: ashr1_sext_i32:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfx x0, x0, #1, #31
+; CHECK-NEXT: ret
+ %1 = ashr i32 %val, 1
+ %2 = sext i32 %1 to i64
+ ret i64 %2
+}
+
+define i64 @ashr31_sext_i32(i32 %val) nounwind {
+; CHECK-LABEL: ashr31_sext_i32:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfx x0, x0, #31, #1
+; CHECK-NEXT: ret
+ %1 = ashr i32 %val, 31
+ %2 = sext i32 %1 to i64
+ ret i64 %2
+}
+
+define i64 @sext_ashr1_sext_i8(i8 %val) nounwind {
+; CHECK-LABEL: sext_ashr1_sext_i8:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfx x0, x0, #1, #7
+; CHECK-NEXT: ret
+ %1 = sext i8 %val to i32
+ %2 = ashr i32 %1, 1
+ %3 = sext i32 %2 to i64
+ ret i64 %3
+}
+
+define i64 @sext_ashr7_sext_i8(i8 %val) nounwind {
+; CHECK-LABEL: sext_ashr7_sext_i8:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfx x0, x0, #7, #1
+; CHECK-NEXT: ret
+ %1 = sext i8 %val to i32
+ %2 = ashr i32 %1, 7
+ %3 = sext i32 %2 to i64
+ ret i64 %3
+}
+
+define i64 @sext_ashr1_sext_i16(i16 %val) nounwind {
+; CHECK-LABEL: sext_ashr1_sext_i16:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfx x0, x0, #1, #15
+; CHECK-NEXT: ret
+ %1 = sext i16 %val to i32
+ %2 = ashr i32 %1, 1
+ %3 = sext i32 %2 to i64
+ ret i64 %3
+}
+
+define i64 @sext_ashr15_sext_i16(i16 %val) nounwind {
+; CHECK-LABEL: sext_ashr15_sext_i16:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: ; kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfx x0, x0, #15, #1
+; CHECK-NEXT: ret
+ %1 = sext i16 %val to i32
+ %2 = ashr i32 %1, 15
+ %3 = sext i32 %2 to i64
+ ret i64 %3
+}
diff --git a/llvm/test/CodeGen/AArch64/lslfast.ll b/llvm/test/CodeGen/AArch64/lslfast.ll
index 5ec70b5f22975..239de3efc5161 100644
--- a/llvm/test/CodeGen/AArch64/lslfast.ll
+++ b/llvm/test/CodeGen/AArch64/lslfast.ll
@@ -96,8 +96,8 @@ entry:
define i64 @test3sext(i32 noundef %x, i64 noundef %y, i64 noundef %z) {
; CHECK-LABEL: test3sext:
; CHECK: // %bb.0: // %entry
-; CHECK-NEXT: lsl w8, w0, #3
-; CHECK-NEXT: sxtw x8, w8
+; CHECK-NEXT: // kill: def $w0 killed $w0 def $x0
+; CHECK-NEXT: sbfiz x8, x0, #3, #29
; CHECK-NEXT: add x9, x8, x1
; CHECK-NEXT: add x8, x8, x2
; CHECK-NEXT: mul x0, x9, x8
More information about the llvm-commits
mailing list