[llvm] ab39c49 - [X86][APX] Reuse EFLAGS across multi-predecessor blocks via NF in optimizeCompareInstr (#208184)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 19 06:38:25 PDT 2026


Author: Feng Zou
Date: 2026-07-19T21:38:20+08:00
New Revision: ab39c491430e1f327849708ababffa3458b8ff6f

URL: https://github.com/llvm/llvm-project/commit/ab39c491430e1f327849708ababffa3458b8ff6f
DIFF: https://github.com/llvm/llvm-project/commit/ab39c491430e1f327849708ababffa3458b8ff6f.diff

LOG: [X86][APX] Reuse EFLAGS across multi-predecessor blocks via NF in optimizeCompareInstr (#208184)

Extend optimizeCompareInstr to reuse EFLAGS produced in predecessor
blocks (including via NF variants) when a compare is redundant, covering
multi-predecessor, dominating, and cyclic CFGs. Share the NF-clobber
removal helper with FlagsCopyLowering, correctly handle swapped and
immediate-adjusted flag reuse, and simplify EFLAGS live-in marking.

Adds MIR tests exercising multi-predecessor reuse over cyclic and
dominating CFGs, including a multi-level idom walk.

Assisted-By: Claude Opus 4.8

Added: 
    llvm/test/CodeGen/X86/apx/optimize-compare-multipred.ll
    llvm/test/CodeGen/X86/apx/optimize-compare-multipred.mir

Modified: 
    llvm/lib/Target/X86/X86FlagsCopyLowering.cpp
    llvm/lib/Target/X86/X86InstrInfo.cpp
    llvm/lib/Target/X86/X86InstrInfo.h
    llvm/test/CodeGen/X86/apx/add.ll
    llvm/test/CodeGen/X86/apx/flags-copy-lowering.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Target/X86/X86FlagsCopyLowering.cpp b/llvm/lib/Target/X86/X86FlagsCopyLowering.cpp
index 36b17cab03ee9..48c148f882456 100644
--- a/llvm/lib/Target/X86/X86FlagsCopyLowering.cpp
+++ b/llvm/lib/Target/X86/X86FlagsCopyLowering.cpp
@@ -68,8 +68,6 @@ STATISTIC(NumTestsInserted, "Number of test instructions inserted");
 STATISTIC(NumAddsInserted, "Number of adds instructions inserted");
 STATISTIC(NumNFsConvertedTo, "Number of NF instructions converted to");
 
-extern cl::opt<bool> X86EnableAPXForRelocation;
-
 namespace {
 
 // Convenient array type for storing registers associated with each condition.
@@ -254,14 +252,7 @@ static EFLAGSClobber getClobberType(const MachineInstr &MI) {
   if (!FlagDef)
     return NoClobber;
 
-  // For the instructions are ADDrm/ADDmr with relocation, we'll skip the
-  // optimization for replacing non-NF with NF. This is to keep backward
-  // compatiblity with old version of linkers without APX relocation type
-  // support on Linux OS.
-  bool IsWithReloc =
-      X86EnableAPXForRelocation ? false : isAddMemInstrWithRelocation(MI);
-
-  if (FlagDef->isDead() && X86::getNFVariant(MI.getOpcode()) && !IsWithReloc)
+  if (X86::getNFVariantIfClobberRemovable(MI))
     return EvitableClobber;
 
   return InevitableClobber;

diff  --git a/llvm/lib/Target/X86/X86InstrInfo.cpp b/llvm/lib/Target/X86/X86InstrInfo.cpp
index cbbfeb84e5838..f41a68d7d17e0 100644
--- a/llvm/lib/Target/X86/X86InstrInfo.cpp
+++ b/llvm/lib/Target/X86/X86InstrInfo.cpp
@@ -23,7 +23,6 @@
 #include "llvm/CodeGen/LivePhysRegs.h"
 #include "llvm/CodeGen/LiveVariables.h"
 #include "llvm/CodeGen/MachineConstantPool.h"
-#include "llvm/CodeGen/MachineDominators.h"
 #include "llvm/CodeGen/MachineFrameInfo.h"
 #include "llvm/CodeGen/MachineInstr.h"
 #include "llvm/CodeGen/MachineInstrBuilder.h"
@@ -82,6 +81,12 @@ static cl::opt<unsigned> UndefRegClearance(
              "certain undef register reads"),
     cl::init(128), cl::Hidden);
 
+static cl::opt<unsigned> MaxNFConversions(
+    "x86-max-nf-conversions-for-cmp-reuse",
+    cl::desc("Maximum number of NF conversions allowed to reuse EFLAGS from a "
+             "producer dominating a multi-predecessor block"),
+    cl::init(6), cl::Hidden);
+
 // Pin the vtable to this file.
 void X86InstrInfo::anchor() {}
 
@@ -3290,6 +3295,19 @@ unsigned X86::getNFVariant(unsigned Opc) {
   return getNewOpcFromTable(X86NFTransformTable, Opc);
 }
 
+unsigned X86::getNFVariantIfClobberRemovable(const MachineInstr &MI,
+                                             const TargetRegisterInfo *TRI) {
+  if (!MI.registerDefIsDead(X86::EFLAGS, TRI))
+    return 0;
+  // For the instructions are ADDrm/ADDmr with relocation, we'll skip the
+  // optimization for replacing non-NF with NF. This is to keep backward
+  // compatiblity with old version of linkers without APX relocation type
+  // support on Linux OS.
+  if (!X86EnableAPXForRelocation && isAddMemInstrWithRelocation(MI))
+    return 0;
+  return X86::getNFVariant(MI.getOpcode());
+}
+
 unsigned X86::getNonNDVariant(unsigned Opc) {
 #if defined(EXPENSIVE_CHECKS) && !defined(NDEBUG)
   // Make sure the tables are sorted.
@@ -5290,6 +5308,97 @@ static std::pair<X86::CondCode, unsigned> isUseDefConvertible(const MachineInstr
 }
 #undef CASE_EVEX
 
+MachineInstr *X86InstrInfo::findDominatingRedundantFlagInstr(
+    MachineInstr &CmpInstr, Register SrcReg, Register SrcReg2, int64_t CmpMask,
+    int64_t CmpValue, MachineBasicBlock *MultiPredMBB, bool &IsSwapped,
+    int64_t &ImmDelta,
+    SmallVectorImpl<std::pair<MachineInstr *, unsigned>> &InstsToUpdate) const {
+  assert(Subtarget.hasNF() && "NF feature required");
+  const TargetRegisterInfo *TRI = &getRegisterInfo();
+
+  // The caller already scanned MultiPredMBB without finding the producer, so it
+  // must live in a block that strictly dominates MultiPredMBB. Walk
+  // predecessors backward to find it and prove dominance, avoiding a
+  // whole-function MachineDominatorTree that would be rebuilt in O(function
+  // size) per compare.
+  //
+  // The producer's block dominates MultiPredMBB iff every backward path funnels
+  // through it before a function-entry block, so expand predecessors but stop
+  // at a block holding the producer. Bail if a predecessor-less block is
+  // reached without the producer (a path bypasses it) or the producer is found
+  // in two blocks (neither dominates alone). Within a block, scan backward,
+  // collecting the NF-convertible EFLAGS clobbers above the producer and
+  // bailing on any other clobber (it would shadow the producer's flags from
+  // CmpInstr).
+  //
+  // Clobbers are staged in Pending and committed only on success. Visited
+  // (seeded with MultiPredMBB) stops the walk from revisiting a block or
+  // re-entering the single-predecessor chain, so none is collected twice.
+  //
+  // Each NF conversion trades a compact legacy/EVEX-compressed encoding for a
+  // wider EVEX (often NDD three-operand) one, growing code size, while the
+  // reuse only removes a single compare. Cap the total number of conversions
+  // (those the caller already collected on the single-predecessor chain plus
+  // those the walk stages) so the reuse cannot bloat code just to delete one
+  // compare.
+  MachineInstr *Sub = nullptr;
+  MachineBasicBlock *SubMBB = nullptr;
+  SmallVector<std::pair<MachineInstr *, unsigned>, 4> Pending;
+  SmallPtrSet<MachineBasicBlock *, 8> Visited;
+  SmallVector<MachineBasicBlock *, 8> Worklist;
+  Visited.insert(MultiPredMBB);
+  for (MachineBasicBlock *Pred : MultiPredMBB->predecessors())
+    if (Visited.insert(Pred).second)
+      Worklist.push_back(Pred);
+  while (!Worklist.empty()) {
+    MachineBasicBlock *MBB = Worklist.pop_back_val();
+    MachineInstr *Producer = nullptr;
+    for (MachineInstr &Inst : reverse(*MBB)) {
+      if (!Inst.modifiesRegister(X86::EFLAGS, TRI))
+        continue;
+      if (isRedundantFlagInstr(CmpInstr, SrcReg, SrcReg2, CmpMask, CmpValue,
+                               Inst, &IsSwapped, &ImmDelta)) {
+        Producer = &Inst;
+        break;
+      }
+      unsigned NewOpc = X86::getNFVariantIfClobberRemovable(Inst, TRI);
+      if (!NewOpc)
+        return nullptr;
+      if (InstsToUpdate.size() + Pending.size() >= MaxNFConversions)
+        return nullptr;
+      Pending.push_back(std::make_pair(&Inst, NewOpc));
+    }
+    if (Producer) {
+      // A producer in a second block means neither dominates alone.
+      if (Sub && SubMBB != MBB)
+        return nullptr;
+      Sub = Producer;
+      SubMBB = MBB;
+      continue;
+    }
+    // Entry reached without the producer: some path bypasses it.
+    if (MBB->pred_empty())
+      return nullptr;
+    for (MachineBasicBlock *Pred : MBB->predecessors())
+      if (Visited.insert(Pred).second)
+        Worklist.push_back(Pred);
+  }
+  if (!Sub)
+    return nullptr;
+
+  // The forward condition-code fixup in the caller (OpsToUpdate) only rewrites
+  // EFLAGS users within CmpMBB. When the producer's flags require a condition
+  // swap or an immediate adjustment, EFLAGS users elsewhere in the dominated
+  // region or in CmpMBB's successors (when EFLAGS is live-out) would also need
+  // rewriting, which is not handled here. Restrict the multi-predecessor case
+  // to producers that yield identical flags.
+  if (IsSwapped || ImmDelta != 0)
+    return nullptr;
+
+  InstsToUpdate.append(Pending.begin(), Pending.end());
+  return Sub;
+}
+
 /// Check if there exists an earlier instruction that
 /// operates on the same source operands and sets flags in the same way as
 /// Compare; remove Compare if possible.
@@ -5453,17 +5562,9 @@ bool X86InstrInfo::optimizeCompareInstr(MachineInstr &CmpInstr, Register SrcReg,
           continue;
         }
 
-        // For the instructions are ADDrm/ADDmr with relocation, we'll skip the
-        // optimization for replacing non-NF with NF. This is to keep backward
-        // compatiblity with old version of linkers without APX relocation type
-        // support on Linux OS.
-        bool IsWithReloc = X86EnableAPXForRelocation
-                               ? false
-                               : isAddMemInstrWithRelocation(Inst);
-
         // Try to replace non-NF with NF instructions.
-        if (HasNF && Inst.registerDefIsDead(X86::EFLAGS, TRI) && !IsWithReloc) {
-          unsigned NewOp = X86::getNFVariant(Inst.getOpcode());
+        if (HasNF) {
+          unsigned NewOp = X86::getNFVariantIfClobberRemovable(Inst, TRI);
           if (!NewOp)
             return false;
 
@@ -5479,10 +5580,30 @@ bool X86InstrInfo::optimizeCompareInstr(MachineInstr &CmpInstr, Register SrcReg,
     if (MI || Sub)
       break;
 
-    // Reached begin of basic block. Continue in predecessor if there is
-    // exactly one.
-    if (MBB->pred_size() != 1)
-      return false;
+    // Reached the begin of the basic block. If it has exactly one predecessor,
+    // continue the backward scan there. Otherwise (multiple predecessors), try
+    // to reuse EFLAGS from a dominating producer (handled below).
+    if (MBB->pred_size() != 1) {
+      // The block has multiple predecessors. We can still reuse EFLAGS from an
+      // equivalent flag producer that dominates CmpInstr, provided every path
+      // from that producer to CmpInstr only clobbers EFLAGS via instructions
+      // that have an NF (no-flags) variant (which requires APX). This handles
+      // patterns like (CMP duplicated by CodeGenPrepare across a diamond):
+      //   entry:  cmp %x, C   ; br
+      //   bb1:    imul ...     ; clobbers EFLAGS  ->  {nf} imul
+      //   bb2:    ...
+      //   bb3:    cmp %x, C    ; <-- redundant, reuse EFLAGS from entry
+      //           cmovcc ...
+      // The helper caps the total number of NF conversions so this cannot grow
+      // code size without bound just to delete one compare.
+      if (HasNF)
+        Sub = findDominatingRedundantFlagInstr(
+            CmpInstr, SrcReg, SrcReg2, CmpMask, CmpValue, MBB, IsSwapped,
+            ImmDelta, InstsToUpdate);
+      if (!Sub)
+        return false;
+      break;
+    }
     MBB = *MBB->pred_begin();
     From = MBB->rbegin();
   }
@@ -5688,11 +5809,25 @@ bool X86InstrInfo::optimizeCompareInstr(MachineInstr &CmpInstr, Register SrcReg,
         .setImm(Op.second);
   }
   // Add EFLAGS to block live-ins between CmpBB and block of flags producer.
-  for (MachineBasicBlock *MBB = &CmpMBB; MBB != SubBB;
-       MBB = *MBB->pred_begin()) {
-    assert(MBB->pred_size() == 1 && "Expected exactly one predecessor");
+  // Walk the CFG backward from CmpMBB up to (but excluding) SubBB, marking
+  // EFLAGS live-in on every block in between. SubBB dominates CmpMBB (whether
+  // the producer was found by the single-predecessor backward walk or the
+  // multi-predecessor dominator search), so the walk reaches SubBB on every
+  // path and never escapes above it. A single-predecessor chain is just the
+  // degenerate case where every block has exactly one predecessor.
+  SmallPtrSet<MachineBasicBlock *, 8> Visited;
+  SmallVector<MachineBasicBlock *, 8> Worklist(1, &CmpMBB);
+  Visited.insert(&CmpMBB);
+  while (!Worklist.empty()) {
+    MachineBasicBlock *MBB = Worklist.pop_back_val();
+    // EFLAGS is produced inside SubBB, so it is not live-in there.
+    if (MBB == SubBB)
+      continue;
     if (!MBB->isLiveIn(X86::EFLAGS))
       MBB->addLiveIn(X86::EFLAGS);
+    for (MachineBasicBlock *Pred : MBB->predecessors())
+      if (Visited.insert(Pred).second)
+        Worklist.push_back(Pred);
   }
   return true;
 }

diff  --git a/llvm/lib/Target/X86/X86InstrInfo.h b/llvm/lib/Target/X86/X86InstrInfo.h
index 2db0731da3c56..06f942d4fdd9c 100644
--- a/llvm/lib/Target/X86/X86InstrInfo.h
+++ b/llvm/lib/Target/X86/X86InstrInfo.h
@@ -83,6 +83,15 @@ int getCCMPCondFlagsFromCondCode(CondCode CC);
 // Get the opcode of corresponding NF variant.
 unsigned getNFVariant(unsigned Opc);
 
+// If \p MI clobbers EFLAGS and that clobber can be removed by rewriting the
+// instruction to its NF (no-flags) variant, return that NF opcode; otherwise
+// return 0. The clobber is removable only if the EFLAGS def is dead, an NF
+// variant exists, and (for linker backward-compat) it is not an ADDrm/ADDmr
+// with relocation.
+unsigned
+getNFVariantIfClobberRemovable(const MachineInstr &MI,
+                               const TargetRegisterInfo *TRI = nullptr);
+
 // Get the opcode of corresponding NonND variant.
 unsigned getNonNDVariant(unsigned Opc);
 
@@ -765,6 +774,25 @@ class X86InstrInfo final : public X86GenInstrInfo {
                             const MachineInstr &OI, bool *IsSwapped,
                             int64_t *ImmDelta) const;
 
+  /// Used by optimizeCompareInstr when the backward search for an equivalent
+  /// flag producer reaches a block (\p MultiPredMBB) with multiple
+  /// predecessors. Searches the dominator tree for an instruction that produces
+  /// the same flags as the compare \p CmpInstr and dominates it, such that on
+  /// every path from that producer to \p CmpInstr all EFLAGS clobbers can be
+  /// converted to their NF (no-flags) variants. Those clobbers are appended to
+  /// \p InstsToUpdate as (instruction, NF opcode) pairs for the caller to
+  /// rewrite. Restricted to producers that yield identical flags: it succeeds
+  /// only when isRedundantFlagInstr reports no operand swap and no immediate
+  /// delta, so on success \p IsSwapped is false and \p ImmDelta is 0. Returns
+  /// the flag producer on success, or nullptr otherwise. Requires the NF
+  /// feature (APX).
+  MachineInstr *findDominatingRedundantFlagInstr(
+      MachineInstr &CmpInstr, Register SrcReg, Register SrcReg2,
+      int64_t CmpMask, int64_t CmpValue, MachineBasicBlock *MultiPredMBB,
+      bool &IsSwapped, int64_t &ImmDelta,
+      SmallVectorImpl<std::pair<MachineInstr *, unsigned>> &InstsToUpdate)
+      const;
+
   /// Commute operands of \p MI for memory fold.
   ///
   /// \param Idx1 the index of operand to be commuted.

diff  --git a/llvm/test/CodeGen/X86/apx/add.ll b/llvm/test/CodeGen/X86/apx/add.ll
index d7c5635b617c1..8b1e1ceb832a3 100644
--- a/llvm/test/CodeGen/X86/apx/add.ll
+++ b/llvm/test/CodeGen/X86/apx/add.ll
@@ -1221,26 +1221,24 @@ define i32 @two_address_no_subreg(i32 %arg0, ptr %arg1, i1 %arg2) nounwind {
 ; NF-NEXT:    jne .LBB50_2 # encoding: [0x75,A]
 ; NF-NEXT:    # fixup A - offset: 1, value: .LBB50_2, kind: FK_PCRel_1
 ; NF-NEXT:  # %bb.1: # %bb2
-; NF-NEXT:    xorl %r12d, %r12d # encoding: [0x45,0x31,0xe4]
+; NF-NEXT:    movl $0, %r12d # encoding: [0x41,0xbc,0x00,0x00,0x00,0x00]
 ; NF-NEXT:  .LBB50_2: # %bb1
-; NF-NEXT:    movl %r13d, %esi # encoding: [0x44,0x89,0xee]
-; NF-NEXT:    addl (%rsp), %esi # 4-byte Folded Reload
-; NF-NEXT:    # encoding: [0x03,0x34,0x24]
-; NF-NEXT:    {nf} imull %ebx, %ebx, %r13d # EVEX TO EVEX Compression encoding: [0x62,0xf4,0x14,0x1c,0xaf,0xdb]
-; NF-NEXT:    testb $1, %bpl # encoding: [0x40,0xf6,0xc5,0x01]
+; NF-NEXT:    movq (%rsp), %rax # 8-byte Reload
+; NF-NEXT:    # encoding: [0x48,0x8b,0x04,0x24]
+; NF-NEXT:    {nf} addl %eax, %r13d, %ebp # encoding: [0x62,0xd4,0x54,0x1c,0x01,0xc5]
+; NF-NEXT:    {nf} imull %ebx, %ebx, %r13d # encoding: [0x62,0xf4,0x14,0x1c,0xaf,0xdb]
 ; NF-NEXT:    jne .LBB50_4 # encoding: [0x75,A]
 ; NF-NEXT:    # fixup A - offset: 1, value: .LBB50_4, kind: FK_PCRel_1
 ; NF-NEXT:  # %bb.3: # %bb4
-; NF-NEXT:    xorl %ebp, %ebp # encoding: [0x31,0xed]
+; NF-NEXT:    xorl %eax, %eax # encoding: [0x31,0xc0]
 ; NF-NEXT:    movq %r14, %rdi # encoding: [0x4c,0x89,0xf7]
-; NF-NEXT:    movl %esi, %r14d # encoding: [0x41,0x89,0xf6]
-; NF-NEXT:    callq *%rbp # encoding: [0xff,0xd5]
+; NF-NEXT:    callq *%rax # encoding: [0xff,0xd0]
+; NF-NEXT:    xorl %eax, %eax # encoding: [0x31,0xc0]
 ; NF-NEXT:    {nf} incl %r12d, %r15d # EVEX TO EVEX Compression encoding: [0x62,0xd4,0x04,0x1c,0xff,0xc4]
 ; NF-NEXT:    xorl %edi, %edi # encoding: [0x31,0xff]
-; NF-NEXT:    callq *%rbp # encoding: [0xff,0xd5]
-; NF-NEXT:    movl %r14d, %esi # encoding: [0x44,0x89,0xf6]
+; NF-NEXT:    callq *%rax # encoding: [0xff,0xd0]
 ; NF-NEXT:  .LBB50_4: # %bb3
-; NF-NEXT:    orl %r13d, %esi # EVEX TO LEGACY Compression encoding: [0x44,0x09,0xee]
+; NF-NEXT:    {nf} orl %r13d, %ebp, %esi # EVEX TO EVEX Compression encoding: [0x62,0x74,0x4c,0x1c,0x09,0xed]
 ; NF-NEXT:    orl %r15d, %esi # EVEX TO LEGACY Compression encoding: [0x44,0x09,0xfe]
 ; NF-NEXT:    xorl %eax, %eax # encoding: [0x31,0xc0]
 ; NF-NEXT:    movl %ebx, %edi # encoding: [0x89,0xdf]

diff  --git a/llvm/test/CodeGen/X86/apx/flags-copy-lowering.ll b/llvm/test/CodeGen/X86/apx/flags-copy-lowering.ll
index 0dcda8efdbc78..357126bff417d 100644
--- a/llvm/test/CodeGen/X86/apx/flags-copy-lowering.ll
+++ b/llvm/test/CodeGen/X86/apx/flags-copy-lowering.ll
@@ -51,7 +51,6 @@ define <2 x i128> @flag_copy_2(<2 x i128> %x, <2 x i128> %y) nounwind {
   ret <2 x i128> %z
 }
 
-; TODO: Remove the 2nd cmpl by using NF imul.
 define void @flag_copy_3(i32 %x, i32 %y, ptr %pa, ptr %pb, ptr %pc) nounwind {
 ; CHECK-LABEL: flag_copy_3:
 ; CHECK:       # %bb.0: # %entry
@@ -60,14 +59,13 @@ define void @flag_copy_3(i32 %x, i32 %y, ptr %pa, ptr %pb, ptr %pc) nounwind {
 ; CHECK-NEXT:    jl .LBB2_2
 ; CHECK-NEXT:  # %bb.1: # %bb1
 ; CHECK-NEXT:    movl %edi, %eax
-; CHECK-NEXT:    imull %esi, %eax
+; CHECK-NEXT:    {nf} imull %esi, %eax
 ; CHECK-NEXT:    movl %eax, (%rdx)
 ; CHECK-NEXT:    jmp .LBB2_3
 ; CHECK-NEXT:  .LBB2_2: # %bb2
 ; CHECK-NEXT:    leal -2(%rsi), %eax
 ; CHECK-NEXT:    movl %eax, (%rcx)
 ; CHECK-NEXT:  .LBB2_3: # %bb3
-; CHECK-NEXT:    cmpl $2, %edi
 ; CHECK-NEXT:    cmovgel %edi, %esi
 ; CHECK-NEXT:    movl %esi, (%r8)
 ; CHECK-NEXT:    retq

diff  --git a/llvm/test/CodeGen/X86/apx/optimize-compare-multipred.ll b/llvm/test/CodeGen/X86/apx/optimize-compare-multipred.ll
new file mode 100644
index 0000000000000..0cf2469f78ba3
--- /dev/null
+++ b/llvm/test/CodeGen/X86/apx/optimize-compare-multipred.ll
@@ -0,0 +1,483 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+nf | FileCheck %s --check-prefixes=NF
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown | FileCheck %s --check-prefixes=NONF
+
+; Tests for optimizeCompareInstr reusing EFLAGS across a block with multiple
+; predecessors via findDominatingRedundantFlagInstr (requires APX NF feature).
+; A compare duplicated by CodeGenPrepare across a diamond can be removed when
+; every path from the dominating producer to the redundant compare only
+; clobbers EFLAGS through NF-convertible instructions.
+
+declare void @ext()
+ at g = external global i32
+
+; Positive: the entry compare dominates the redundant compare in bb3; the only
+; EFLAGS clobber on the path (imul on bb1) is NF-convertible, so the second
+; compare is removed and the imul becomes {nf} imul.
+define void @diamond_one_arm(i32 %x, i32 %y, ptr %pa, ptr %pb, ptr %pc) nounwind {
+; NF-LABEL: diamond_one_arm:
+; NF:       # %bb.0: # %entry
+; NF-NEXT:    # kill: def $esi killed $esi def $rsi
+; NF-NEXT:    cmpl $2, %edi
+; NF-NEXT:    jl .LBB0_2
+; NF-NEXT:  # %bb.1: # %bb1
+; NF-NEXT:    movl %edi, %eax
+; NF-NEXT:    {nf} imull %esi, %eax
+; NF-NEXT:    movl %eax, (%rdx)
+; NF-NEXT:    jmp .LBB0_3
+; NF-NEXT:  .LBB0_2: # %bb2
+; NF-NEXT:    leal -2(%rsi), %eax
+; NF-NEXT:    movl %eax, (%rcx)
+; NF-NEXT:  .LBB0_3: # %bb3
+; NF-NEXT:    cmovgel %edi, %esi
+; NF-NEXT:    movl %esi, (%r8)
+; NF-NEXT:    retq
+;
+; NONF-LABEL: diamond_one_arm:
+; NONF:       # %bb.0: # %entry
+; NONF-NEXT:    # kill: def $esi killed $esi def $rsi
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    jl .LBB0_2
+; NONF-NEXT:  # %bb.1: # %bb1
+; NONF-NEXT:    movl %edi, %eax
+; NONF-NEXT:    imull %esi, %eax
+; NONF-NEXT:    movl %eax, (%rdx)
+; NONF-NEXT:    jmp .LBB0_3
+; NONF-NEXT:  .LBB0_2: # %bb2
+; NONF-NEXT:    leal -2(%rsi), %eax
+; NONF-NEXT:    movl %eax, (%rcx)
+; NONF-NEXT:  .LBB0_3: # %bb3
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    cmovgel %edi, %esi
+; NONF-NEXT:    movl %esi, (%r8)
+; NONF-NEXT:    retq
+entry:
+  %cmp = icmp sgt i32 %x, 1
+  br i1 %cmp, label %bb1, label %bb2
+bb1:
+  %mul = mul nuw nsw i32 %x, %y
+  store i32 %mul, ptr %pa
+  br label %bb3
+bb2:
+  %sub = sub nuw nsw i32 %y, 2
+  store i32 %sub, ptr %pb
+  br label %bb3
+bb3:
+  %s = select i1 %cmp, i32 %x, i32 %y
+  store i32 %s, ptr %pc
+  ret void
+}
+
+; Positive: NF-convertible EFLAGS clobbers on BOTH diamond arms. The backward
+; CFG walk collects both, converts both to {nf}, and removes the redundant
+; compare.
+define void @diamond_both_arms(i32 %x, i32 %y, ptr %pa, ptr %pb, ptr %pc) nounwind {
+; NF-LABEL: diamond_both_arms:
+; NF:       # %bb.0: # %entry
+; NF-NEXT:    cmpl $2, %edi
+; NF-NEXT:    jl .LBB1_2
+; NF-NEXT:  # %bb.1: # %bb1
+; NF-NEXT:    movl %edi, %eax
+; NF-NEXT:    {nf} imull %esi, %eax
+; NF-NEXT:    movl %eax, (%rdx)
+; NF-NEXT:    jmp .LBB1_3
+; NF-NEXT:  .LBB1_2: # %bb2
+; NF-NEXT:    movl %esi, %eax
+; NF-NEXT:    {nf} imull %esi, %eax
+; NF-NEXT:    movl %eax, (%rcx)
+; NF-NEXT:  .LBB1_3: # %bb3
+; NF-NEXT:    cmovgel %edi, %esi
+; NF-NEXT:    movl %esi, (%r8)
+; NF-NEXT:    retq
+;
+; NONF-LABEL: diamond_both_arms:
+; NONF:       # %bb.0: # %entry
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    jl .LBB1_2
+; NONF-NEXT:  # %bb.1: # %bb1
+; NONF-NEXT:    movl %edi, %eax
+; NONF-NEXT:    imull %esi, %eax
+; NONF-NEXT:    movl %eax, (%rdx)
+; NONF-NEXT:    jmp .LBB1_3
+; NONF-NEXT:  .LBB1_2: # %bb2
+; NONF-NEXT:    movl %esi, %eax
+; NONF-NEXT:    imull %esi, %eax
+; NONF-NEXT:    movl %eax, (%rcx)
+; NONF-NEXT:  .LBB1_3: # %bb3
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    cmovgel %edi, %esi
+; NONF-NEXT:    movl %esi, (%r8)
+; NONF-NEXT:    retq
+entry:
+  %cmp = icmp sgt i32 %x, 1
+  br i1 %cmp, label %bb1, label %bb2
+bb1:
+  %m1 = mul nuw nsw i32 %x, %y
+  store i32 %m1, ptr %pa
+  br label %bb3
+bb2:
+  %m2 = mul nuw nsw i32 %y, %y
+  store i32 %m2, ptr %pb
+  br label %bb3
+bb3:
+  %s = select i1 %cmp, i32 %x, i32 %y
+  store i32 %s, ptr %pc
+  ret void
+}
+
+; Positive: the producer dominates the multi-predecessor block %join several
+; levels up (idom chain %join -> %pre -> %entry), and every EFLAGS clobber on
+; the paths is an NF-convertible imul. Both redundant compares are removed: the
+; one in %pre by the single-predecessor backward walk (its only predecessor is
+; %entry), and the one in %join by the multi-predecessor dominator search.
+define void @nested_dominator(i32 %x, i32 %y, ptr %pa, ptr %pb, ptr %pc, ptr %pd) nounwind {
+; NF-LABEL: nested_dominator:
+; NF:       # %bb.0: # %entry
+; NF-NEXT:    cmpl $2, %edi
+; NF-NEXT:    jl .LBB2_5
+; NF-NEXT:  # %bb.1: # %pre
+; NF-NEXT:    movl %edi, %eax
+; NF-NEXT:    {nf} imull %esi, %eax
+; NF-NEXT:    movl %eax, (%rdx)
+; NF-NEXT:    jl .LBB2_3
+; NF-NEXT:  # %bb.2: # %bb1
+; NF-NEXT:    movl %edi, %eax
+; NF-NEXT:    {nf} imull %edi, %eax
+; NF-NEXT:    movl %eax, (%rcx)
+; NF-NEXT:    jmp .LBB2_4
+; NF-NEXT:  .LBB2_3: # %bb2
+; NF-NEXT:    movl %esi, %eax
+; NF-NEXT:    {nf} imull %esi, %eax
+; NF-NEXT:    movl %eax, (%r8)
+; NF-NEXT:  .LBB2_4: # %join
+; NF-NEXT:    cmovgel %edi, %esi
+; NF-NEXT:    movl %esi, (%r9)
+; NF-NEXT:  .LBB2_5: # %ret
+; NF-NEXT:    retq
+;
+; NONF-LABEL: nested_dominator:
+; NONF:       # %bb.0: # %entry
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    jl .LBB2_5
+; NONF-NEXT:  # %bb.1: # %pre
+; NONF-NEXT:    movl %edi, %eax
+; NONF-NEXT:    imull %esi, %eax
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    movl %eax, (%rdx)
+; NONF-NEXT:    jl .LBB2_3
+; NONF-NEXT:  # %bb.2: # %bb1
+; NONF-NEXT:    movl %edi, %eax
+; NONF-NEXT:    imull %edi, %eax
+; NONF-NEXT:    movl %eax, (%rcx)
+; NONF-NEXT:    jmp .LBB2_4
+; NONF-NEXT:  .LBB2_3: # %bb2
+; NONF-NEXT:    movl %esi, %eax
+; NONF-NEXT:    imull %esi, %eax
+; NONF-NEXT:    movl %eax, (%r8)
+; NONF-NEXT:  .LBB2_4: # %join
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    cmovgel %edi, %esi
+; NONF-NEXT:    movl %esi, (%r9)
+; NONF-NEXT:  .LBB2_5: # %ret
+; NONF-NEXT:    retq
+entry:
+  %cmp = icmp sgt i32 %x, 1
+  br i1 %cmp, label %pre, label %ret
+pre:
+  %m0 = mul nuw nsw i32 %x, %y
+  store i32 %m0, ptr %pa
+  br i1 %cmp, label %bb1, label %bb2
+bb1:
+  %m1 = mul nuw nsw i32 %x, %x
+  store i32 %m1, ptr %pb
+  br label %join
+bb2:
+  %m2 = mul nuw nsw i32 %y, %y
+  store i32 %m2, ptr %pc
+  br label %join
+join:
+  %s = select i1 %cmp, i32 %x, i32 %y
+  store i32 %s, ptr %pd
+  br label %ret
+ret:
+  ret void
+}
+
+; Negative: a call clobbers EFLAGS on the path (regmask clobber, no NF variant),
+; so the redundant compare must be kept.
+define void @call_on_path(i32 %x, i32 %y, ptr %pa, ptr %pc) nounwind {
+; NF-LABEL: call_on_path:
+; NF:       # %bb.0: # %entry
+; NF-NEXT:    pushq %rbp
+; NF-NEXT:    pushq %r15
+; NF-NEXT:    pushq %r14
+; NF-NEXT:    pushq %rbx
+; NF-NEXT:    pushq %rax
+; NF-NEXT:    movq %rcx, %rbx
+; NF-NEXT:    movl %esi, %ebp
+; NF-NEXT:    movl %edi, %r14d
+; NF-NEXT:    cmpl $2, %edi
+; NF-NEXT:    jl .LBB3_2
+; NF-NEXT:  # %bb.1: # %bb1
+; NF-NEXT:    movq %rdx, %r15
+; NF-NEXT:    callq ext at PLT
+; NF-NEXT:    movl %ebp, (%r15)
+; NF-NEXT:  .LBB3_2: # %bb3
+; NF-NEXT:    cmpl $2, %r14d
+; NF-NEXT:    cmovgel %r14d, %ebp
+; NF-NEXT:    movl %ebp, (%rbx)
+; NF-NEXT:    addq $8, %rsp
+; NF-NEXT:    popq %rbx
+; NF-NEXT:    popq %r14
+; NF-NEXT:    popq %r15
+; NF-NEXT:    popq %rbp
+; NF-NEXT:    retq
+;
+; NONF-LABEL: call_on_path:
+; NONF:       # %bb.0: # %entry
+; NONF-NEXT:    pushq %rbp
+; NONF-NEXT:    pushq %r15
+; NONF-NEXT:    pushq %r14
+; NONF-NEXT:    pushq %rbx
+; NONF-NEXT:    pushq %rax
+; NONF-NEXT:    movq %rcx, %rbx
+; NONF-NEXT:    movl %esi, %ebp
+; NONF-NEXT:    movl %edi, %r14d
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    jl .LBB3_2
+; NONF-NEXT:  # %bb.1: # %bb1
+; NONF-NEXT:    movq %rdx, %r15
+; NONF-NEXT:    callq ext at PLT
+; NONF-NEXT:    movl %ebp, (%r15)
+; NONF-NEXT:  .LBB3_2: # %bb3
+; NONF-NEXT:    cmpl $2, %r14d
+; NONF-NEXT:    cmovgel %r14d, %ebp
+; NONF-NEXT:    movl %ebp, (%rbx)
+; NONF-NEXT:    addq $8, %rsp
+; NONF-NEXT:    popq %rbx
+; NONF-NEXT:    popq %r14
+; NONF-NEXT:    popq %r15
+; NONF-NEXT:    popq %rbp
+; NONF-NEXT:    retq
+entry:
+  %cmp = icmp sgt i32 %x, 1
+  br i1 %cmp, label %bb1, label %bb2
+bb1:
+  call void @ext()
+  store i32 %y, ptr %pa
+  br label %bb3
+bb2:
+  br label %bb3
+bb3:
+  %s = select i1 %cmp, i32 %x, i32 %y
+  store i32 %s, ptr %pc
+  ret void
+}
+
+; Negative: a flag-consuming compare/branch (testl + je) sits on the path; its
+; EFLAGS def is not dead, so it is not NF-convertible and the redundant compare
+; is kept.
+define void @flag_user_on_path(i32 %x, i32 %y, ptr %pa, ptr %pb, ptr %pc) nounwind {
+; NF-LABEL: flag_user_on_path:
+; NF:       # %bb.0: # %entry
+; NF-NEXT:    cmpl $2, %edi
+; NF-NEXT:    jl .LBB4_3
+; NF-NEXT:  # %bb.1: # %bb1
+; NF-NEXT:    testl %esi, %esi
+; NF-NEXT:    je .LBB4_3
+; NF-NEXT:  # %bb.2: # %bb2
+; NF-NEXT:    movl %esi, (%rcx)
+; NF-NEXT:  .LBB4_3: # %bb3
+; NF-NEXT:    cmpl $2, %edi
+; NF-NEXT:    cmovgel %edi, %esi
+; NF-NEXT:    movl %esi, (%r8)
+; NF-NEXT:    retq
+;
+; NONF-LABEL: flag_user_on_path:
+; NONF:       # %bb.0: # %entry
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    jl .LBB4_3
+; NONF-NEXT:  # %bb.1: # %bb1
+; NONF-NEXT:    testl %esi, %esi
+; NONF-NEXT:    je .LBB4_3
+; NONF-NEXT:  # %bb.2: # %bb2
+; NONF-NEXT:    movl %esi, (%rcx)
+; NONF-NEXT:  .LBB4_3: # %bb3
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    cmovgel %edi, %esi
+; NONF-NEXT:    movl %esi, (%r8)
+; NONF-NEXT:    retq
+entry:
+  %cmp = icmp sgt i32 %x, 1
+  br i1 %cmp, label %bb1, label %bb3
+bb1:
+  %nz = icmp ne i32 %y, 0
+  br i1 %nz, label %bb2, label %bb3
+bb2:
+  store i32 %y, ptr %pb
+  br label %bb3
+bb3:
+  %s = select i1 %cmp, i32 %x, i32 %y
+  store i32 %s, ptr %pc
+  ret void
+}
+
+; Negative: the redundant compare uses swapped operands (cmp y, x vs cmp x, y).
+; The multi-predecessor path is restricted to identical flags, so it bails.
+define void @swapped_operands(i32 %x, i32 %y, ptr %pa, ptr %pb, ptr %pc) nounwind {
+; NF-LABEL: swapped_operands:
+; NF:       # %bb.0: # %entry
+; NF-NEXT:    cmpl %esi, %edi
+; NF-NEXT:    jle .LBB5_2
+; NF-NEXT:  # %bb.1: # %bb1
+; NF-NEXT:    movl %edi, %eax
+; NF-NEXT:    imull %esi, %eax
+; NF-NEXT:    movl %eax, (%rdx)
+; NF-NEXT:    jmp .LBB5_3
+; NF-NEXT:  .LBB5_2: # %bb2
+; NF-NEXT:    movl %esi, (%rcx)
+; NF-NEXT:  .LBB5_3: # %bb3
+; NF-NEXT:    cmpl %edi, %esi
+; NF-NEXT:    cmovll %edi, %esi
+; NF-NEXT:    movl %esi, (%r8)
+; NF-NEXT:    retq
+;
+; NONF-LABEL: swapped_operands:
+; NONF:       # %bb.0: # %entry
+; NONF-NEXT:    cmpl %esi, %edi
+; NONF-NEXT:    jle .LBB5_2
+; NONF-NEXT:  # %bb.1: # %bb1
+; NONF-NEXT:    movl %edi, %eax
+; NONF-NEXT:    imull %esi, %eax
+; NONF-NEXT:    movl %eax, (%rdx)
+; NONF-NEXT:    jmp .LBB5_3
+; NONF-NEXT:  .LBB5_2: # %bb2
+; NONF-NEXT:    movl %esi, (%rcx)
+; NONF-NEXT:  .LBB5_3: # %bb3
+; NONF-NEXT:    cmpl %edi, %esi
+; NONF-NEXT:    cmovll %edi, %esi
+; NONF-NEXT:    movl %esi, (%r8)
+; NONF-NEXT:    retq
+entry:
+  %cmp = icmp sgt i32 %x, %y
+  br i1 %cmp, label %bb1, label %bb2
+bb1:
+  %mul = mul nuw nsw i32 %x, %y
+  store i32 %mul, ptr %pa
+  br label %bb3
+bb2:
+  store i32 %y, ptr %pb
+  br label %bb3
+bb3:
+  %cmp2 = icmp slt i32 %y, %x
+  %s = select i1 %cmp2, i32 %x, i32 %y
+  store i32 %s, ptr %pc
+  ret void
+}
+
+; Negative: the redundant compare 
diff ers by an immediate delta (x>1 vs x>2),
+; which would require a condition-code adjustment; the multi-predecessor path
+; bails.
+define void @imm_delta(i32 %x, i32 %y, ptr %pa, ptr %pb, ptr %pc) nounwind {
+; NF-LABEL: imm_delta:
+; NF:       # %bb.0: # %entry
+; NF-NEXT:    cmpl $2, %edi
+; NF-NEXT:    jl .LBB6_2
+; NF-NEXT:  # %bb.1: # %bb1
+; NF-NEXT:    movl %edi, %eax
+; NF-NEXT:    imull %esi, %eax
+; NF-NEXT:    movl %eax, (%rdx)
+; NF-NEXT:    jmp .LBB6_3
+; NF-NEXT:  .LBB6_2: # %bb2
+; NF-NEXT:    movl %esi, (%rcx)
+; NF-NEXT:  .LBB6_3: # %bb3
+; NF-NEXT:    cmpl $3, %edi
+; NF-NEXT:    cmovgel %edi, %esi
+; NF-NEXT:    movl %esi, (%r8)
+; NF-NEXT:    retq
+;
+; NONF-LABEL: imm_delta:
+; NONF:       # %bb.0: # %entry
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    jl .LBB6_2
+; NONF-NEXT:  # %bb.1: # %bb1
+; NONF-NEXT:    movl %edi, %eax
+; NONF-NEXT:    imull %esi, %eax
+; NONF-NEXT:    movl %eax, (%rdx)
+; NONF-NEXT:    jmp .LBB6_3
+; NONF-NEXT:  .LBB6_2: # %bb2
+; NONF-NEXT:    movl %esi, (%rcx)
+; NONF-NEXT:  .LBB6_3: # %bb3
+; NONF-NEXT:    cmpl $3, %edi
+; NONF-NEXT:    cmovgel %edi, %esi
+; NONF-NEXT:    movl %esi, (%r8)
+; NONF-NEXT:    retq
+entry:
+  %cmp = icmp sgt i32 %x, 1
+  br i1 %cmp, label %bb1, label %bb2
+bb1:
+  %mul = mul nuw nsw i32 %x, %y
+  store i32 %mul, ptr %pa
+  br label %bb3
+bb2:
+  store i32 %y, ptr %pb
+  br label %bb3
+bb3:
+  %cmp2 = icmp sgt i32 %x, 2
+  %s = select i1 %cmp2, i32 %x, i32 %y
+  store i32 %s, ptr %pc
+  ret void
+}
+
+; Negative: no dominating producer compares the same value, so there is nothing
+; to reuse and the compare is kept.
+define void @no_producer(i32 %x, i32 %y, i32 %z, ptr %pa, ptr %pb, ptr %pc) nounwind {
+; NF-LABEL: no_producer:
+; NF:       # %bb.0: # %entry
+; NF-NEXT:    cmpl $2, %edi
+; NF-NEXT:    jl .LBB7_2
+; NF-NEXT:  # %bb.1: # %bb1
+; NF-NEXT:    movl %edi, %eax
+; NF-NEXT:    imull %esi, %eax
+; NF-NEXT:    movl %eax, (%rcx)
+; NF-NEXT:    jmp .LBB7_3
+; NF-NEXT:  .LBB7_2: # %bb2
+; NF-NEXT:    movl %esi, (%r8)
+; NF-NEXT:  .LBB7_3: # %bb3
+; NF-NEXT:    cmpl $6, %edx
+; NF-NEXT:    cmovgel %edi, %esi
+; NF-NEXT:    movl %esi, (%r9)
+; NF-NEXT:    retq
+;
+; NONF-LABEL: no_producer:
+; NONF:       # %bb.0: # %entry
+; NONF-NEXT:    cmpl $2, %edi
+; NONF-NEXT:    jl .LBB7_2
+; NONF-NEXT:  # %bb.1: # %bb1
+; NONF-NEXT:    movl %edi, %eax
+; NONF-NEXT:    imull %esi, %eax
+; NONF-NEXT:    movl %eax, (%rcx)
+; NONF-NEXT:    jmp .LBB7_3
+; NONF-NEXT:  .LBB7_2: # %bb2
+; NONF-NEXT:    movl %esi, (%r8)
+; NONF-NEXT:  .LBB7_3: # %bb3
+; NONF-NEXT:    cmpl $6, %edx
+; NONF-NEXT:    cmovgel %edi, %esi
+; NONF-NEXT:    movl %esi, (%r9)
+; NONF-NEXT:    retq
+entry:
+  %cmp = icmp sgt i32 %x, 1
+  br i1 %cmp, label %bb1, label %bb2
+bb1:
+  %mul = mul nuw nsw i32 %x, %y
+  store i32 %mul, ptr %pa
+  br label %bb3
+bb2:
+  store i32 %y, ptr %pb
+  br label %bb3
+bb3:
+  %cmp2 = icmp sgt i32 %z, 5
+  %s = select i1 %cmp2, i32 %x, i32 %y
+  store i32 %s, ptr %pc
+  ret void
+}

diff  --git a/llvm/test/CodeGen/X86/apx/optimize-compare-multipred.mir b/llvm/test/CodeGen/X86/apx/optimize-compare-multipred.mir
new file mode 100644
index 0000000000000..a6ff8900a0349
--- /dev/null
+++ b/llvm/test/CodeGen/X86/apx/optimize-compare-multipred.mir
@@ -0,0 +1,354 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
+# RUN: llc -o - %s -mtriple=x86_64-- -run-pass peephole-opt -mattr=+nf | FileCheck %s
+
+# MIR-level coverage for the multi-predecessor EFLAGS reuse in
+# optimizeCompareInstr / findDominatingRedundantFlagInstr. These CFG shapes are
+# awkward to produce from IR (LICM hoists loop-invariant compares, the scheduler
+# repositions EFLAGS clobbers), so they are written directly as MIR.
+
+# A non-NF-convertible EFLAGS clobber in the dominating block that defines the
+# flags shadows the producer, so the redundant compare in bb.3 must be kept.
+---
+name: dom_shadow_clobber
+tracksRegLiveness: true
+body: |
+  ; CHECK-LABEL: name: dom_shadow_clobber
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.2(0x40000000), %bb.1(0x40000000)
+  ; CHECK-NEXT:   liveins: $edi, $esi
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gr32 = COPY $edi
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gr32 = COPY $esi
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   CMP32ri [[COPY1]], 7, implicit-def $eflags
+  ; CHECK-NEXT:   JCC_1 %bb.2, 12, implicit $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[IMUL32rr:%[0-9]+]]:gr32 = nuw nsw IMUL32rr [[COPY]], [[COPY1]], implicit-def dead $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.3:
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   $al = SETCCr 15, implicit $eflags
+  ; CHECK-NEXT:   RET 0, $al
+  bb.0:
+    liveins: $edi, $esi
+    %0:gr32 = COPY $edi
+    %1:gr32 = COPY $esi
+    CMP32ri %0, 2, implicit-def $eflags
+    CMP32ri %1, 7, implicit-def $eflags
+    JCC_1 %bb.2, 12, implicit $eflags
+    JMP_1 %bb.1
+
+  bb.1:
+    %2:gr32 = nuw nsw IMUL32rr %0, %1, implicit-def dead $eflags
+    JMP_1 %bb.3
+
+  bb.2:
+
+  bb.3:
+    CMP32ri %0, 2, implicit-def $eflags
+    $al = SETCCr 15, implicit $eflags
+    RET 0, $al
+...
+
+# Same shape WITHOUT the shadow clobber: the producer in the dominating block is
+# found, the redundant compare in bb.3 is removed and the IMUL on the path is
+# rewritten to its NF variant. Positive control for dom_shadow_clobber.
+---
+name: dom_no_shadow
+tracksRegLiveness: true
+body: |
+  ; CHECK-LABEL: name: dom_no_shadow
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.2(0x40000000), %bb.1(0x40000000)
+  ; CHECK-NEXT:   liveins: $edi, $esi
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gr32 = COPY $edi
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gr32 = COPY $esi
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   JCC_1 %bb.2, 12, implicit $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT:   liveins: $eflags
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[IMUL32rr_NF:%[0-9]+]]:gr32 = nuw nsw IMUL32rr_NF [[COPY]], [[COPY1]]
+  ; CHECK-NEXT:   JMP_1 %bb.3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT:   liveins: $eflags
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.3:
+  ; CHECK-NEXT:   liveins: $eflags
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   $al = SETCCr 15, implicit $eflags
+  ; CHECK-NEXT:   RET 0, $al
+  bb.0:
+    liveins: $edi, $esi
+    %0:gr32 = COPY $edi
+    %1:gr32 = COPY $esi
+    CMP32ri %0, 2, implicit-def $eflags
+    JCC_1 %bb.2, 12, implicit $eflags
+    JMP_1 %bb.1
+
+  bb.1:
+    %2:gr32 = nuw nsw IMUL32rr %0, %1, implicit-def dead $eflags
+    JMP_1 %bb.3
+
+  bb.2:
+
+  bb.3:
+    CMP32ri %0, 2, implicit-def $eflags
+    $al = SETCCr 15, implicit $eflags
+    RET 0, $al
+...
+
+# Cap: the producer in bb.0 dominates the multi-predecessor block bb.3, but the
+# path to the redundant compare in bb.3 has more NF-convertible EFLAGS clobbers
+# (7 IMULs on the bb.1 arm) than the conversion cap allows. Converting that many
+# instructions to delete one compare is a net size regression, so the reuse is
+# declined and the compare in bb.3 is kept.
+---
+name: too_many_conversions
+tracksRegLiveness: true
+body: |
+  ; CHECK-LABEL: name: too_many_conversions
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.2(0x40000000), %bb.1(0x40000000)
+  ; CHECK-NEXT:   liveins: $edi, $esi
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gr32 = COPY $edi
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gr32 = COPY $esi
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   JCC_1 %bb.2, 12, implicit $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[IMUL32rr:%[0-9]+]]:gr32 = nuw nsw IMUL32rr [[COPY]], [[COPY1]], implicit-def dead $eflags
+  ; CHECK-NEXT:   [[IMUL32rr1:%[0-9]+]]:gr32 = nuw nsw IMUL32rr [[IMUL32rr]], [[COPY1]], implicit-def dead $eflags
+  ; CHECK-NEXT:   [[IMUL32rr2:%[0-9]+]]:gr32 = nuw nsw IMUL32rr [[IMUL32rr1]], [[COPY1]], implicit-def dead $eflags
+  ; CHECK-NEXT:   [[IMUL32rr3:%[0-9]+]]:gr32 = nuw nsw IMUL32rr [[IMUL32rr2]], [[COPY1]], implicit-def dead $eflags
+  ; CHECK-NEXT:   [[IMUL32rr4:%[0-9]+]]:gr32 = nuw nsw IMUL32rr [[IMUL32rr3]], [[COPY1]], implicit-def dead $eflags
+  ; CHECK-NEXT:   [[IMUL32rr5:%[0-9]+]]:gr32 = nuw nsw IMUL32rr [[IMUL32rr4]], [[COPY1]], implicit-def dead $eflags
+  ; CHECK-NEXT:   [[IMUL32rr6:%[0-9]+]]:gr32 = nuw nsw IMUL32rr [[IMUL32rr5]], [[COPY1]], implicit-def dead $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.3:
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   $al = SETCCr 15, implicit $eflags
+  ; CHECK-NEXT:   RET 0, $al
+  bb.0:
+    liveins: $edi, $esi
+    %0:gr32 = COPY $edi
+    %1:gr32 = COPY $esi
+    CMP32ri %0, 2, implicit-def $eflags
+    JCC_1 %bb.2, 12, implicit $eflags
+    JMP_1 %bb.1
+
+  bb.1:
+    %2:gr32 = nuw nsw IMUL32rr %0, %1, implicit-def dead $eflags
+    %3:gr32 = nuw nsw IMUL32rr %2, %1, implicit-def dead $eflags
+    %4:gr32 = nuw nsw IMUL32rr %3, %1, implicit-def dead $eflags
+    %5:gr32 = nuw nsw IMUL32rr %4, %1, implicit-def dead $eflags
+    %6:gr32 = nuw nsw IMUL32rr %5, %1, implicit-def dead $eflags
+    %7:gr32 = nuw nsw IMUL32rr %6, %1, implicit-def dead $eflags
+    %8:gr32 = nuw nsw IMUL32rr %7, %1, implicit-def dead $eflags
+    JMP_1 %bb.3
+
+  bb.2:
+
+  bb.3:
+    CMP32ri %0, 2, implicit-def $eflags
+    $al = SETCCr 15, implicit $eflags
+    RET 0, $al
+...
+
+# Loop: the producer in bb.0 dominates the multi-predecessor block bb.1 (preds
+# bb.0 and the bb.3 back-edge). The redundant compare in bb.1 is removed and the
+# NF-convertible IMUL on the bb.2 path is rewritten. This exercises the backward
+# walk over a cyclic CFG with a back-edge (the Visited set keeps it
+# terminating), which the acyclic diamonds in optimize-compare-multipred.ll do
+# not cover.
+---
+name: loop_backedge
+tracksRegLiveness: true
+body: |
+  ; CHECK-LABEL: name: loop_backedge
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $edi, $esi
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gr32 = COPY $edi
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gr32 = COPY $esi
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.4(0x40000000), %bb.2(0x40000000)
+  ; CHECK-NEXT:   liveins: $eflags
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   JCC_1 %bb.4, 4, implicit $eflags
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT:   liveins: $eflags
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[IMUL32rr_NF:%[0-9]+]]:gr32 = nuw nsw IMUL32rr_NF [[COPY]], [[COPY1]]
+  ; CHECK-NEXT:   JMP_1 %bb.3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.3:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $eflags
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   JMP_1 %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.4:
+  ; CHECK-NEXT:   liveins: $eflags
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   $al = SETCCr 15, implicit $eflags
+  ; CHECK-NEXT:   RET 0, $al
+  bb.0:
+    liveins: $edi, $esi
+    %0:gr32 = COPY $edi
+    %1:gr32 = COPY $esi
+    CMP32ri %0, 2, implicit-def $eflags
+    JMP_1 %bb.1
+
+  bb.1:
+    CMP32ri %0, 2, implicit-def $eflags
+    JCC_1 %bb.4, 4, implicit $eflags
+
+  bb.2:
+    %2:gr32 = nuw nsw IMUL32rr %0, %1, implicit-def dead $eflags
+    JMP_1 %bb.3
+
+  bb.3:
+    JMP_1 %bb.1
+
+  bb.4:
+    liveins: $eflags
+    $al = SETCCr 15, implicit $eflags
+    RET 0, $al
+...
+
+# The producer lives in bb.2, an immediate predecessor of the multi-predecessor
+# block bb.3, but bb.3's other predecessor bb.1 reaches it without passing
+# through bb.2, so bb.2 does not dominate bb.3. The backward walk reaches the
+# entry block bb.0 (predecessor-less) without finding the producer on that path
+# and bails, so the redundant compare in bb.3 is kept.
+---
+name: producer_in_nondominating_pred
+tracksRegLiveness: true
+body: |
+  ; CHECK-LABEL: name: producer_in_nondominating_pred
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.2(0x40000000), %bb.1(0x40000000)
+  ; CHECK-NEXT:   liveins: $edi, $esi
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gr32 = COPY $edi
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gr32 = COPY $esi
+  ; CHECK-NEXT:   JCC_1 %bb.2, 4, implicit undef $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   JMP_1 %bb.3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.3:
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   $al = SETCCr 15, implicit $eflags
+  ; CHECK-NEXT:   RET 0, $al
+  bb.0:
+    liveins: $edi, $esi
+    %0:gr32 = COPY $edi
+    %1:gr32 = COPY $esi
+    JCC_1 %bb.2, 4, implicit undef $eflags
+    JMP_1 %bb.1
+
+  bb.1:
+    JMP_1 %bb.3
+
+  bb.2:
+    CMP32ri %0, 2, implicit-def $eflags
+    JMP_1 %bb.3
+
+  bb.3:
+    CMP32ri %0, 2, implicit-def $eflags
+    $al = SETCCr 15, implicit $eflags
+    RET 0, $al
+...
+
+# The producer is duplicated on both diamond arms bb.1 and bb.2, so neither
+# block alone dominates the multi-predecessor block bb.3. The backward walk
+# finds a producer in two distinct blocks and bails, keeping the compare.
+---
+name: producer_in_two_blocks
+tracksRegLiveness: true
+body: |
+  ; CHECK-LABEL: name: producer_in_two_blocks
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.2(0x40000000), %bb.1(0x40000000)
+  ; CHECK-NEXT:   liveins: $edi, $esi
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gr32 = COPY $edi
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gr32 = COPY $esi
+  ; CHECK-NEXT:   JCC_1 %bb.2, 4, implicit undef $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   successors: %bb.3(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   JMP_1 %bb.3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.3:
+  ; CHECK-NEXT:   CMP32ri [[COPY]], 2, implicit-def $eflags
+  ; CHECK-NEXT:   $al = SETCCr 15, implicit $eflags
+  ; CHECK-NEXT:   RET 0, $al
+  bb.0:
+    liveins: $edi, $esi
+    %0:gr32 = COPY $edi
+    %1:gr32 = COPY $esi
+    JCC_1 %bb.2, 4, implicit undef $eflags
+    JMP_1 %bb.1
+
+  bb.1:
+    CMP32ri %0, 2, implicit-def $eflags
+    JMP_1 %bb.3
+
+  bb.2:
+    CMP32ri %0, 2, implicit-def $eflags
+    JMP_1 %bb.3
+
+  bb.3:
+    CMP32ri %0, 2, implicit-def $eflags
+    $al = SETCCr 15, implicit $eflags
+    RET 0, $al
+...


        


More information about the llvm-commits mailing list