[llvm] [X86] Lower vector integer division and remainder through float division (PR #205263)

Adam Scott via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 4 19:50:24 PDT 2026


================
@@ -50839,6 +50847,140 @@ static SDValue combineMulToPMADD52(SDNode *N, const SDLoc &DL,
   return SDValue();
 }
 
+// x86 has no vector integer divide instructions. Lower vector
+// UDIV/SDIV/UREM/SREM through float division instead of scalarizing into N
+// scalar hardware divides.
+static SDValue combineIntDivRem(SDNode *N, SelectionDAG &DAG,
+                                TargetLowering::DAGCombinerInfo &DCI,
+                                const X86Subtarget &Subtarget) {
+  EVT VT = N->getValueType(0);
+  SDLoc DL(N);
+
+  // Run before the legalizer expands the division.
+  if (!VT.isVector() || !DCI.isBeforeLegalizeOps())
+    return SDValue();
+
+  // Don't introduce a trapping FP divide under strict FP.
+  if (DAG.getMachineFunction().getFunction().hasFnAttribute(
+          Attribute::StrictFP))
+    return SDValue();
+
+  SDValue Dividend = N->getOperand(0);
+  SDValue Divisor = N->getOperand(1);
+  unsigned Opc = N->getOpcode();
+
+  // Disabled lanes are poison and fdiv never traps, so ignore the mask.
+  if (Opc == ISD::MASKED_UDIV || Opc == ISD::MASKED_SDIV ||
+      Opc == ISD::MASKED_UREM || Opc == ISD::MASKED_SREM)
+    Opc = ISD::getUnmaskedBinOpOpcode(Opc);
+  bool IsRem = Opc == ISD::UREM || Opc == ISD::SREM;
+  bool IsSigned = Opc == ISD::SDIV || Opc == ISD::SREM;
+
+  // If the result is only read back as scalar extracts, scalarization computes
+  // just the demanded lanes.
+  if (all_of(N->users(), [](const SDNode *U) {
+        return U->getOpcode() == ISD::EXTRACT_VECTOR_ELT;
+      }))
+    return SDValue();
+
+  // Magic multiply lowers constant divisors cheaper than a divide.
+  if (DAG.isConstantIntBuildVectorOrConstantInt(Divisor))
+    return SDValue();
+
+  // i8/i16/i32: operands fit the float mantissa exactly (f32 for <=16-bit, f64
+  // for 32-bit) so one float divide recovers the exact quotient.
+  if (VT.getScalarSizeInBits() <= 32 && Subtarget.hasSSE2()) {
+    // Unsigned i32 needs FP_TO_UINT(f64->u32) which is emulated and a loss
+    // for latency and code size before AVX2.
+    if (!IsSigned && VT.getScalarSizeInBits() == 32 && !Subtarget.hasAVX2())
+      return SDValue();
+
+    MVT FPSclVT = VT.getScalarSizeInBits() <= 16 ? MVT::f32 : MVT::f64;
+    EVT FPVT = VT.changeVectorElementType(*DAG.getContext(), FPSclVT);
+
+    // Nothing will split an illegal FP type after type legalization.
+    if (!DCI.isBeforeLegalize() &&
+        !DAG.getTargetLoweringInfo().isTypeLegal(FPVT))
+      return SDValue();
+
+    unsigned ToFP = IsSigned ? ISD::SINT_TO_FP : ISD::UINT_TO_FP;
+    unsigned FromFP = IsSigned ? ISD::FP_TO_SINT : ISD::FP_TO_UINT;
+    SDValue X = DAG.getNode(ToFP, DL, FPVT, Dividend);
+    SDValue Y = DAG.getNode(ToFP, DL, FPVT, Divisor);
+    SDValue Quot = DAG.getNode(ISD::FDIV, DL, FPVT, X, Y);
+    SDValue Q = DAG.getNode(FromFP, DL, VT, Quot);
+    if (!IsRem)
+      return Q;
+    // rem = dividend - quotient * divisor
+    return DAG.getNode(ISD::SUB, DL, VT, Dividend,
+                       DAG.getNode(ISD::MUL, DL, VT, Q, Divisor));
+  }
+
+  // i64: the quotient doesn't fit f64 exactly, so build it from two
+  // rounded-down reciprocal multiplies, one of the dividend and one of its
+  // remainder. {rd/ru-sae} rounding is 512-bit only, so v8i64 on AVX512DQ.
----------------
as4230 wrote:

Measured on rocket lake (i7-11700) and got this with perf

```
scalar   2,381,280,236 cycles
v2i64    2,381,806,658 cycles
v4i64    1,397,040,308 cycles
v8i64      986,304,028 cycles
```

v2i64 ties scalar while going from 11 instructions to 24 so not seeing the same win from the Godbolt above. I will leave out for now and can revisit if needed.

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


More information about the llvm-commits mailing list