[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