[llvm] [RISCV] Fold vmand of mask comparisons into a masked comparison (PR #216264)

Min-Yih Hsu via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 14 12:04:19 PDT 2026


================
@@ -787,6 +788,169 @@ bool RISCVVectorPeephole::foldVMergeToMask(MachineInstr &MI) const {
   return true;
 }
 
+/// Fold a mask-register AND of a mask comparison into a mask-undisturbed
+/// masked comparison, saving an instruction:
+///
+///   %cmp1 = PseudoVMSLT_VV_M1 %a, %b, %vl, %sew
+///   %cmp2 = PseudoVMSLT_VV_M1 %c, %d, %vl, %sew
+///   %and  = PseudoVMAND_MM %cmp1, %cmp2, %vl, 0
+/// ->
+///   %cmp1 = PseudoVMSLT_VV_M1 %a, %b, %vl, %sew
+///   %and  = PseudoVMSLT_VV_M1_MASK %cmp1, %c, %d, %cmp1, %vl, %sew, mu
+///
+/// This works because for a mask-undisturbed masked compare whose passthru is
+/// the same register as its mask %m, the result is %m[i] ? (c cmp d)[i] :
+/// %m[i], which is exactly %m[i] & (c cmp d)[i], i.e. vmand(%m, vmscmp(c, d)).
+///
+/// Since vmand is commutative it's enough for either operand to be a foldable
+/// comparison; the other operand becomes both the mask and the passthru.
+bool RISCVVectorPeephole::foldVMANDToMaskedCompare(MachineInstr &MI) const {
+  if (RISCV::getRVVMCOpcode(MI.getOpcode()) != RISCV::VMAND_MM)
+    return false;
+
+  // The masked comparison we create needs its mask (and passthru) in v0, which
+  // the original vmand did not require. If the vmand's result has more than one
+  // use then it is an interior mask value rather than a final result feeding
+  // v0, and introducing the v0 requirement tends to add vmv1r.v moves. Only
+  // fold single-use results, where the value coalesces onto v0 for free.
+  if (!MRI->hasOneUse(MI.getOperand(0).getReg()))
+    return false;
+
+  // Try each operand as the comparison to be masked; the other becomes the
+  // mask/passthru.
+  for (unsigned CmpIdx : {1, 2}) {
+    unsigned MaskIdx = CmpIdx == 1 ? 2 : 1;
+
+    // The comparison must be single use so that folding it into MI doesn't
+    // leave an extra unmasked comparison behind.
+    SmallVector<MachineInstr *, 4> CmpCopies;
+    Register CmpReg = lookThruCopies(MI.getOperand(CmpIdx).getReg(),
+                                     /*OneUseOnly=*/true, &CmpCopies);
+    if (!CmpReg.isVirtual() || !MRI->hasOneUse(CmpReg))
+      continue;
+    MachineInstr &Cmp = *MRI->getUniqueVRegDef(CmpReg);
+    if (Cmp.getParent() != MI.getParent())
+      continue;
+
+    // Only fold comparisons: entries in the masked pseudo table whose unmasked
+    // form has neither a passthru nor a policy operand. This excludes ops like
+    // vmsbf.m/viota.m which share that shape but whose active elements depend
+    // on the mask, so masking them would change their result.
+    const RISCV::RISCVMaskedPseudoInfo *Info =
+        RISCV::lookupMaskedIntrinsicByUnmasked(Cmp.getOpcode());
+    if (!Info)
+      continue;
+    const MCInstrDesc &UnmaskedDesc = Cmp.getDesc();
+    if (RISCVII::isFirstDefTiedToFirstUse(UnmaskedDesc) ||
+        RISCVII::hasVecPolicyOp(UnmaskedDesc.TSFlags))
+      continue;
+    unsigned CmpMCOpc = RISCV::getRVVMCOpcode(Cmp.getOpcode());
+    if (RISCVII::elementsDependOnMask(TII->get(CmpMCOpc).TSFlags))
+      continue;
+
+    // The EEW of the comparison's dest must match vmand's SEW.
+    if (!hasSameEEW(MI, Cmp))
+      continue;
+
+    // Masking restricts the comparison to the mask's active elements, so any FP
+    // exceptions raised on inactive elements would be lost.
+    if (Cmp.hasUnmodeledSideEffects() || Cmp.mayRaiseFPException())
+      continue;
+
+    // Use the smaller of the two VLs for the result. The comparison's inactive
+    // (and tail) elements will be filled from the mask, so if vmand's VL is the
+    // smaller one we still need the comparison's original VL for the elements
+    // between them to come from the mask rather than the raw comparison.
+    const MachineOperand &CmpVL =
+        Cmp.getOperand(RISCVII::getVLOpNum(Cmp.getDesc()));
+    const MachineOperand &MIVL =
+        MI.getOperand(RISCVII::getVLOpNum(MI.getDesc()));
+    MachineOperand MinVL = MachineOperand::CreateImm(0);
+    if (RISCV::isVLKnownLE(CmpVL, MIVL))
+      MinVL = CmpVL;
+    else if (RISCV::isVLKnownLE(MIVL, CmpVL))
+      MinVL = MIVL;
+    else
+      continue;
+
+    const MachineOperand &MaskOp = MI.getOperand(MaskIdx);
+    Register MaskReg = MaskOp.getReg();
+
+    unsigned MaskedOpc = Info->MaskedPseudo;
+    const MCInstrDesc &MaskedDesc = TII->get(MaskedOpc);
+    unsigned SEW = Cmp.getOperand(RISCVII::getSEWOpNum(Cmp.getDesc())).getImm();
+
+    // Only fold if the masked comparison's dest can live in v0. Its mask
+    // operand must be v0, and we reuse the mask as the passthru, so if the dest
+    // can also be v0 the whole thing coalesces onto v0 and we save the vmand
+    // for free. For LMUL >= 2 the dest is earlyclobbered into vrnov0, which
+    // would force extra vmv1r.v moves for the mask and result and make this a
+    // regression, so bail out in that case. This check must happen before we
+    // mutate any instructions below.
+    if (!TII->getRegClass(MaskedDesc, 0)->contains(RISCV::V0))
+      continue;
+
+    // Make sure the mask and VL dominate the comparison, sinking it if needed.
+    if (!ensureDominates({&MaskOp, &MinVL}, Cmp))
+      continue;
+
+    // The masked comparison's mask operand lives in the VMV0 (v0) class, and
+    // its passthru operand shares the dest's class. Copy the vmand mask into
+    // both; the coalescer collapses these back onto v0, matching the
+    // two-instruction ideal.
+    Register MaskV0Reg = MRI->createVirtualRegister(&RISCV::VMV0RegClass);
+    BuildMI(*MI.getParent(), Cmp, Cmp.getDebugLoc(),
+            TII->get(TargetOpcode::COPY), MaskV0Reg)
+        .addReg(MaskReg);
+    Register PassthruReg =
+        MRI->createVirtualRegister(TII->getRegClass(MaskedDesc, 1));
+    BuildMI(*MI.getParent(), Cmp, Cmp.getDebugLoc(),
+            TII->get(TargetOpcode::COPY), PassthruReg)
+        .addReg(MaskReg);
+
+    // Build the masked comparison. Its dest reuses vmand's dest; the passthru
+    // (tied to the dest) and mask are both the other vmand operand. Preserve
+    // the source comparison's MI flags (e.g. nofpexcept), which still hold
+    // since the masked comparison operates on a subset of the original active
+    // elements.
+    Register DestReg = MI.getOperand(0).getReg();
+    MachineInstr *Masked =
+        BuildMI(*MI.getParent(), Cmp, Cmp.getDebugLoc(), MaskedDesc)
+            .addDef(DestReg)
----------------
mshockwave wrote:

```suggestion
        BuildMI(*MI.getParent(), Cmp, MIMetadata(Cmp), MaskedDesc, DestReg)
```

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


More information about the llvm-commits mailing list