[llvm] [AArch64][DAGcombine] Combine (zext i8 * 0x010101...) to NEON dup (PR #194246)
via llvm-commits
llvm-commits at lists.llvm.org
Fri May 1 07:35:20 PDT 2026
https://github.com/llongint updated https://github.com/llvm/llvm-project/pull/194246
>From 5aeda802186721653dc52eec2a191187cc013dd4 Mon Sep 17 00:00:00 2001
From: hezuoqiang <hezuoqiang at huawei.com>
Date: Mon, 27 Apr 2026 08:22:45 +0800
Subject: [PATCH] [AArch64][DAGcombine] Combine (zext i8 * 0x010101...) to NEON
dup
Optimize the pattern where a zero-extended byte is multiplied by
0x010101... to broadcast the byte to all lanes. This replaces
scalar multiply instructions with a more efficient NEON dup.
Before:
mov w8, #0x1010101
ldurb w9, [x0, #-1]
mul w9, w9, w8
stp w9, w9, [x0]
After:
sub x2, x0, #1
ld1r {v0.8b}, [x2]
st1 {v0.8b}, [x0]
---
.../Target/AArch64/AArch64ISelLowering.cpp | 52 +++++++++++++++++++
.../AArch64/neon-broadcast-scalar-mul.ll | 43 +++++++++++++++
2 files changed, 95 insertions(+)
create mode 100644 llvm/test/CodeGen/AArch64/neon-broadcast-scalar-mul.ll
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index e3c12b9ebd3ed..d68d77f8a4e44 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -20346,6 +20346,56 @@ static SDValue performVectorExtCombine(SDNode *N, SelectionDAG &DAG) {
return SDValue();
}
+// Combine (zext i8 to iN) * 0x010101... into a NEON byte broadcast using dup.
+static SDValue performMulBroadcastCombine(SDNode *N, SelectionDAG &DAG,
+ TargetLowering::DAGCombinerInfo &DCI,
+ const AArch64Subtarget *Subtarget) {
+ EVT MulVT = N->getValueType(0);
+ if (!MulVT.isInteger() || MulVT.isVector() || !Subtarget->hasNEON())
+ return SDValue();
+
+ SDValue N0 = N->getOperand(0);
+ SDValue N1 = N->getOperand(1);
+
+ ConstantSDNode *C = dyn_cast<ConstantSDNode>(N1);
+ if (!C)
+ return SDValue();
+
+ const APInt &ConstValue = C->getAPIntValue();
+ unsigned BitWidth = ConstValue.getBitWidth();
+
+ if (N0->getOpcode() != ISD::ZERO_EXTEND ||
+ N0->getOperand(0).getValueType() != MVT::i8)
+ return SDValue();
+
+ // Check if the constant is a byte broadcast pattern: 0x01010101...
+ if (!ConstValue.isSplat(8) || ConstValue.extractBits(8, 0) != 0x01)
+ return SDValue();
+
+ if (BitWidth != 32 && BitWidth != 64)
+ return SDValue();
+
+ if (BitWidth == 32 && !DCI.isBeforeLegalizeOps())
+ return SDValue();
+
+ if (!DAG.getTargetLoweringInfo().isTypeLegal(MVT::v8i8))
+ return SDValue();
+
+ SDLoc DL(N);
+
+ MVT VecVT;
+ if (BitWidth == 32)
+ VecVT = MVT::v4i8; // type legalizer will handle v4i8
+ else if (BitWidth == 64)
+ VecVT = MVT::v8i8;
+
+ // Build a splat vector from the extended byte.
+ SDValue ByteVal = N0->getOperand(0);
+ SDValue BroadcastVec = DAG.getSplatBuildVector(VecVT, DL, ByteVal);
+
+ return DAG.getNode(ISD::BITCAST, DL, MulVT, BroadcastVec);
+}
+
static SDValue performMulCombine(SDNode *N, SelectionDAG &DAG,
TargetLowering::DAGCombinerInfo &DCI,
const AArch64Subtarget *Subtarget) {
@@ -20356,6 +20406,8 @@ static SDValue performMulCombine(SDNode *N, SelectionDAG &DAG,
return Ext;
if (SDValue Ext = performVectorExtCombine(N, DAG))
return Ext;
+ if (SDValue Ext = performMulBroadcastCombine(N, DAG, DCI, Subtarget))
+ return Ext;
if (DCI.isBeforeLegalizeOps())
return SDValue();
diff --git a/llvm/test/CodeGen/AArch64/neon-broadcast-scalar-mul.ll b/llvm/test/CodeGen/AArch64/neon-broadcast-scalar-mul.ll
new file mode 100644
index 0000000000000..720712e2c53e7
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/neon-broadcast-scalar-mul.ll
@@ -0,0 +1,43 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=aarch64-none-linux -mattr=+neon < %s | FileCheck %s
+
+; Test: Load a byte and broadcast to 8 bytes using multiply by 0x0101010101010101
+define void @broadcast_byte_to_8bytes(ptr %dst, ptr %src) nounwind {
+; CHECK-LABEL: broadcast_byte_to_8bytes
+; CHECK: ld1r { v0.8b }, [x1]
+; CHECK-NEXT: str d0, [x0]
+; CHECK-NEXT: ret
+ %val = load i8, ptr %src
+ %ext = zext i8 %val to i64
+ %broadcast = mul i64 %ext, 72340172838076673 ; 0x0101010101010101
+ store i64 %broadcast, ptr %dst
+ ret void
+}
+
+; Test: Load with negative offset
+define void @broadcast_with_negative_offset(ptr %dst, ptr %src) nounwind {
+; CHECK-LABEL: broadcast_with_negative_offset
+; CHECK: sub x8, x1, #1
+; CHECK-NEXT: ld1r { v0.8b }, [x8]
+; CHECK-NEXT: str d0, [x0]
+; CHECK-NEXT: ret
+ %offset.ptr = getelementptr i8, ptr %src, i64 -1
+ %val = load i8, ptr %offset.ptr
+ %ext = zext i8 %val to i64
+ %broadcast = mul i64 %ext, 72340172838076673 ; 0x0101010101010101
+ store i64 %broadcast, ptr %dst
+ ret void
+}
+
+; Test: Load a byte and broadcast to 4 bytes using multiply by 0x01010101
+define void @broadcast_byte_to_4bytes(ptr %dst, ptr %src) nounwind {
+; CHECK-LABEL: broadcast_byte_to_4bytes
+; CHECK: ld1r { v0.8b }, [x1]
+; CHECK-NEXT: str s0, [x0]
+; CHECK-NEXT: ret
+ %val = load i8, ptr %src
+ %ext = zext i8 %val to i32
+ %broadcast = mul i32 %ext, 16843009 ; 0x01010101
+ store i32 %broadcast, ptr %dst
+ ret void
+}
More information about the llvm-commits
mailing list