[llvm] [AArch64][SVE] Use ADD/ADR instead of MUL/MLA for x*N (PR #198566)

Tomas Matheson via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 10 02:05:30 PDT 2026


================
@@ -20579,10 +20618,169 @@ static bool isOnlyUsedAsMemoryAddress(SDNode *N) {
   return true;
 }
 
+/// Build an all-active SVE left shift by a constant element value.
+static SDValue buildSVEShiftLeftImmediate(SDValue Op, unsigned Shift,
+                                          SelectionDAG &DAG, SDLoc DL) {
+  EVT VT = Op.getValueType();
+  assert(VT.isScalableVector() && "Expected scalable vector type");
+
+  EVT ScalarVT = VT.getScalarType();
+  SDValue ShiftSplat = DAG.getNode(ISD::SPLAT_VECTOR, DL, VT,
+                                   DAG.getConstant(Shift, DL, ScalarVT));
+  return DAG.getNode(AArch64ISD::SHL_PRED, DL, VT,
+                     getPredicateForVector(DAG, DL, VT), Op, ShiftSplat);
+}
+
+static bool isSupportedSVEMulByConstantVT(EVT VT) {
+  return VT.isScalableVector() &&
+         (VT.getScalarType() == MVT::i32 || VT.getScalarType() == MVT::i64);
+}
+
+/// Combine an SVE multiply by a constant that does not need MUL:
+///   x * 2     -> x + x
+///   x * 3/5/9 -> x + (x << 1/2/3), which can select ADR.
+static SDValue performSVEMulByConstantCombine(SDValue Op, uint64_t Imm,
+                                              SelectionDAG &DAG, SDLoc DL) {
+  switch (Imm) {
+  default:
+    return SDValue();
+  case 2:
+    return DAG.getNode(ISD::ADD, DL, Op.getValueType(), Op, Op);
+  case 3:
+    return DAG.getNode(ISD::ADD, DL, Op.getValueType(), Op,
+                       buildSVEShiftLeftImmediate(Op, 1, DAG, DL));
+  case 5:
+    return DAG.getNode(ISD::ADD, DL, Op.getValueType(), Op,
+                       buildSVEShiftLeftImmediate(Op, 2, DAG, DL));
+  case 9:
+    return DAG.getNode(ISD::ADD, DL, Op.getValueType(), Op,
+                       buildSVEShiftLeftImmediate(Op, 3, DAG, DL));
+  }
+}
+
+/// Build the scaled multiply operand for:
+///   acc + x * 1     -> acc + x
+///   acc + x * 2/4/8 -> acc + (x << 1/2/3)
+/// The final ADD can then select ADR.
+static SDValue buildSVEScaledMulOperand(SDValue Op, uint64_t Imm,
+                                        SelectionDAG &DAG, SDLoc DL) {
+  switch (Imm) {
+  default:
+    return SDValue();
+  case 1:
+    return Op;
+  case 2:
+    return buildSVEShiftLeftImmediate(Op, 1, DAG, DL);
+  case 4:
+    return buildSVEShiftLeftImmediate(Op, 2, DAG, DL);
+  case 8:
+    return buildSVEShiftLeftImmediate(Op, 3, DAG, DL);
+  }
+}
+
+/// Combine scalable i32/i64 SVE multiply by small constants to ADD/SHL DAG
+/// nodes that avoid MUL and expose ADR selection opportunities.
+static SDValue performSVEMulByConstantNodeCombine(SDNode *N,
+                                                  SelectionDAG &DAG) {
+  EVT VT = N->getValueType(0);
+  bool IsSupportedSVEInt = isSupportedSVEMulByConstantVT(VT);
+  if (!IsSupportedSVEInt)
+    return SDValue();
+
+  SDValue Op = N->getOperand(0);
+  SDValue ImmOp = N->getOperand(1);
+  ConstantSDNode *ImmC = isConstOrConstSplat(ImmOp);
+  if (!ImmC) {
+    // MUL is commutative, so accept the splat on either side.
+    Op = N->getOperand(1);
+    ImmOp = N->getOperand(0);
+    ImmC = isConstOrConstSplat(ImmOp);
+    if (!ImmC)
+      return SDValue();
+  }
+
+  const APInt &Imm = ImmC->getAPIntValue();
+
+  // acc + (x * 2) -> acc + (x << 1), which can select ADR.
+  if (Imm == 2 && N->hasOneUse() && N->user_begin()->getOpcode() == ISD::ADD)
+    return buildSVEShiftLeftImmediate(Op, 1, DAG, SDLoc(N));
+
+  return performSVEMulByConstantCombine(Op, Imm.getZExtValue(), DAG, SDLoc(N));
+}
+
+/// Rewrite all-active SVE MUL intrinsics to the same ADD/SHL forms as plain
+/// arithmetic. Partial predicates are not equivalent because inactive lanes
+/// must keep their predicated intrinsic semantics.
+static SDValue performAllActiveSVEMulIntrinsicCombine(SDNode *N,
+                                                      SelectionDAG &DAG) {
+  if (!isSupportedSVEMulByConstantVT(N->getValueType(0)) ||
+      !isAllActivePredicate(DAG, N->getOperand(1)))
+    return SDValue();
+
+  SDValue Op = N->getOperand(2);
+  SDValue ImmOp = N->getOperand(3);
+  ConstantSDNode *ImmC = isConstOrConstSplat(ImmOp);
+  if (!ImmC) {
+    // MUL is commutative, so accept the splat on either side.
+    Op = N->getOperand(3);
+    ImmOp = N->getOperand(2);
+    ImmC = isConstOrConstSplat(ImmOp);
+    if (!ImmC)
+      return DAG.getNode(ISD::MUL, SDLoc(N), N->getValueType(0),
+                         N->getOperand(2), N->getOperand(3));
+  }
+
+  const APInt &Imm = ImmC->getAPIntValue();
+  if (SDValue Mul =
+          performSVEMulByConstantCombine(Op, Imm.getZExtValue(), DAG, SDLoc(N)))
+    return Mul;
+
+  return DAG.getNode(ISD::MUL, SDLoc(N), N->getValueType(0), N->getOperand(2),
+                     N->getOperand(3));
+}
+
+/// Rewrite all-active SVE MLA intrinsics as ADD plus a scaled multiply operand:
+///   acc + x * 1     -> acc + x
+///   acc + x * 2/4/8 -> acc + (x << 1/2/3)
----------------
tommat01 wrote:

Reimplemented as isel patterns

https://github.com/llvm/llvm-project/pull/198566


More information about the llvm-commits mailing list