[llvm] [IR] Add `llvm.dynamicshuffle` intrinsic for vector shuffles with runtime masks (PR #208537)

Luke Lau via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 13 19:30:19 PDT 2026


================
@@ -14126,6 +14132,79 @@ SDValue RISCVTargetLowering::lowerVectorCompress(SDValue Op,
   return Res;
 }
 
+SDValue RISCVTargetLowering::lowerVECTOR_SHUFFLE_VAR(SDValue Op,
+                                                     SelectionDAG &DAG) const {
+  SDLoc DL(Op);
+  SDValue V1 = Op.getOperand(0);
+  SDValue V2 = Op.getOperand(1);
+  SDValue Mask = Op.getOperand(2);
+  MVT VT = Op.getSimpleValueType();
+  MVT XLenVT = Subtarget.getXLenVT();
+
+  // Only handle shuffles that preserve the input type; anything else takes
+  // the generic expansion.
+  if (V1.getSimpleValueType() != VT)
+    return SDValue();
+
+  // Gather with indices of the data SEW, or i16 (vrgatherei16.vv) when SEW
+  // can't represent the whole two-source index space (i8) or is wider than
+  // XLEN (i64 on RV32).
+  unsigned GatherOpc = RISCVISD::VRGATHER_VV_VL;
+  MVT IndexVT = VT.changeTypeToInteger();
+  if (IndexVT.getScalarType().bitsGT(XLenVT) ||
+      (VT.getScalarSizeInBits() == 8 &&
+       (VT.isScalableVector() || 2 * VT.getVectorNumElements() > 256))) {
+    GatherOpc = RISCVISD::VRGATHEREI16_VV_VL;
+    IndexVT = IndexVT.changeVectorElementType(MVT::i16);
+  }
+
+  MVT ContainerVT = VT;
+  MVT IndexContainerVT = IndexVT;
+  if (VT.isFixedLengthVector()) {
+    ContainerVT = getContainerForFixedLengthVector(VT);
+    IndexContainerVT =
+        ContainerVT.changeVectorElementType(IndexVT.getScalarType());
+  }
+  // vrgatherei16 at SEW=8 needs an index vector of twice the data LMUL; bail
+  // if that (or an RV64 index for RV32 i64 data) isn't a legal type.
+  if (!isTypeLegal(IndexContainerVT))
+    return SDValue();
+
+  // Indices are unsigned; truncation can only wrap out-of-range (poison)
+  // lanes back into range, which poison permits.
+  SDValue Idx = DAG.getZExtOrTrunc(Mask, DL, IndexVT);
+  auto [TrueMask, VL] = getDefaultVLOps(VT, ContainerVT, DL, DAG, Subtarget);
+
+  auto Gather = [&](SDValue Src, SDValue Indices) {
+    if (VT.isFixedLengthVector()) {
+      Src = convertToScalableVector(ContainerVT, Src, DAG, Subtarget);
+      Indices =
+          convertToScalableVector(IndexContainerVT, Indices, DAG, Subtarget);
+    }
+    SDValue G = DAG.getNode(GatherOpc, DL, ContainerVT, Src, Indices,
+                            DAG.getUNDEF(ContainerVT), TrueMask, VL);
+    if (VT.isFixedLengthVector())
+      G = convertFromScalableVector(VT, G, DAG, Subtarget);
+    return G;
+  };
+
+  // vrgather zeroes out-of-range lanes; those are poison here, so a single
+  // gather covers the one-source case and two gathers plus a select on
+  // Idx < NumElts cover the two-source case with no other range checks.
+  SDValue Res = Gather(V1, Idx);
+  if (!V2.isUndef()) {
+    SDValue NumElts = DAG.getSplat(
+        IndexVT, DL,
+        DAG.getElementCount(DL, XLenVT, VT.getVectorElementCount()));
+    SDValue Res2 = Gather(V2, DAG.getNode(ISD::SUB, DL, IndexVT, Idx, NumElts));
+    EVT CCVT =
+        getSetCCResultType(DAG.getDataLayout(), *DAG.getContext(), IndexVT);
+    SDValue FromV1 = DAG.getSetCC(DL, CCVT, Idx, NumElts, ISD::SETULT);
+    Res = DAG.getSelect(DL, VT, FromV1, Res, Res2);
+  }
+  return Res;
+}
----------------
lukel97 wrote:

> There's interest from the AArch64 SVE side of things to use a dynaicshuffle-like intrinsics to more directly target TBL instructions from the loop vectorizer (which is where https://github.com/llvm/llvm-project/pull/208688 came from). Currently, lowering to TBL requires quite a bit of post vectorization pattern matching. 

What pattern in the loop vectorizer does this match? Do we ever dynamically need to shuffle across VF lanes there?

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


More information about the llvm-commits mailing list