Skip to content

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

Open
fzou1 wants to merge 2 commits into
llvm:mainfrom
fzou1:apx-optimize-compare-multipred
Open

[X86][APX] Reuse EFLAGS across multi-predecessor blocks via NF in optimizeCompareInstr#208184
fzou1 wants to merge 2 commits into
llvm:mainfrom
fzou1:apx-optimize-compare-multipred

Conversation

@fzou1

@fzou1 fzou1 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

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

…CompareInstr

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.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fzou1 fzou1 requested review from KanRobert and phoebewang and removed request for phoebewang July 8, 2026 09:38
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-x86

Author: Feng Zou (fzou1)

Changes

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


Patch is 37.99 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/208184.diff

7 Files Affected:

  • (modified) llvm/lib/Target/X86/X86FlagsCopyLowering.cpp (+1-10)
  • (modified) llvm/lib/Target/X86/X86InstrInfo.cpp (+154-17)
  • (modified) llvm/lib/Target/X86/X86InstrInfo.h (+26)
  • (modified) llvm/test/CodeGen/X86/apx/add.ll (+10-12)
  • (modified) llvm/test/CodeGen/X86/apx/flags-copy-lowering.ll (+1-3)
  • (added) llvm/test/CodeGen/X86/apx/optimize-compare-multipred.ll (+483)
  • (added) llvm/test/CodeGen/X86/apx/optimize-compare-multipred.mir (+183)
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 7ff2400d06d1d..e7410ddabee94 100644
--- a/llvm/lib/Target/X86/X86InstrInfo.cpp
+++ b/llvm/lib/Target/X86/X86InstrInfo.cpp
@@ -3290,6 +3290,21 @@ 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.
+  bool IsWithReloc =
+      X86EnableAPXForRelocation ? false : isAddMemInstrWithRelocation(MI);
+  if (IsWithReloc)
+    return 0;
+  return X86::getNFVariant(MI.getOpcode());
+}
+
 unsigned X86::getNonNDVariant(unsigned Opc) {
 #if defined(EXPENSIVE_CHECKS) && !defined(NDEBUG)
   // Make sure the tables are sorted.
@@ -5285,6 +5300,104 @@ static std::pair<X86::CondCode, unsigned> isUseDefConvertible(const MachineInstr
   }
 }
 
+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");
+  MachineBasicBlock &CmpMBB = *CmpInstr.getParent();
+  MachineFunction &MF = *CmpMBB.getParent();
+  const TargetRegisterInfo *TRI = &getRegisterInfo();
+
+  // The caller already scanned MultiPredMBB (and any single-predecessor blocks
+  // between it and CmpMBB) without finding the producer, so the producer must
+  // live in a block that strictly dominates MultiPredMBB. Walk up the
+  // immediate-dominator chain. In each dominating block scan backward: the
+  // redundant producer ends the search; an NF-convertible clobber is stepped
+  // over (it will be collected by the path walk below) so the producer can
+  // still be found earlier in the same block or further up; any other EFLAGS
+  // clobber shadows the producer, so its flags cannot reach CmpInstr.
+  MachineDominatorTree MDT(MF);
+  MachineDomTreeNode *Node = MDT.getNode(MultiPredMBB);
+  MachineInstr *Sub = nullptr;
+  for (Node = Node ? Node->getIDom() : nullptr; Node && !Sub;
+       Node = Node->getIDom()) {
+    MachineBasicBlock *DomMBB = Node->getBlock();
+    for (MachineInstr &Inst : reverse(*DomMBB)) {
+      if (!Inst.modifiesRegister(X86::EFLAGS, TRI))
+        continue;
+      if (isRedundantFlagInstr(CmpInstr, SrcReg, SrcReg2, CmpMask, CmpValue,
+                               Inst, &IsSwapped, &ImmDelta)) {
+        // Found the producer.
+        Sub = &Inst;
+        break;
+      }
+      if (!X86::getNFVariantIfClobberRemovable(Inst, TRI))
+        // A non-convertible clobber shadows any producer further up.
+        return nullptr;
+      // NF-convertible clobber: keep scanning earlier in this block.
+    }
+    // No producer in this block: keep walking up the dominator chain.
+  }
+  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;
+  MachineBasicBlock *SubMBB = Sub->getParent();
+
+  // Verify that on every path from the producer to CmpInstr all EFLAGS clobbers
+  // are NF-convertible, collecting them. The caller already handled CmpMBB, the
+  // single-predecessor chain, and MultiPredMBB itself, so start from
+  // MultiPredMBB's predecessors and walk the CFG backward up to (and including
+  // the post-producer range of) SubMBB. Because SubMBB dominates MultiPredMBB,
+  // every block that can reach MultiPredMBB is dominated by SubMBB, so the walk
+  // reaches SubMBB on every path and never escapes above it.
+  //
+  // The walk never revisits a block (Visited) and never reaches a block the
+  // caller already scanned: MultiPredMBB is seeded into Visited, the
+  // single-predecessor chain blocks (CmpMBB..MultiPredMBB) each have exactly one
+  // predecessor by construction (the caller only advanced through such blocks),
+  // so none can be re-entered from the dominated region, and CmpMBB lies in the
+  // successor direction. No instruction is therefore collected twice.
+  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();
+    // For SubMBB only the range after the producer is relevant.
+    MachineBasicBlock::iterator I =
+        (MBB == SubMBB) ? std::next(MachineBasicBlock::iterator(Sub))
+                        : MBB->begin();
+    for (MachineBasicBlock::iterator E = MBB->end(); I != E; ++I) {
+      MachineInstr &MI = *I;
+      if (!MI.modifiesRegister(X86::EFLAGS, TRI))
+        continue;
+      unsigned NewOpc = X86::getNFVariantIfClobberRemovable(MI, TRI);
+      if (!NewOpc)
+        return nullptr;
+      InstsToUpdate.push_back(std::make_pair(&MI, NewOpc));
+    }
+    if (MBB == SubMBB)
+      continue;
+    for (MachineBasicBlock *Pred : MBB->predecessors())
+      if (Visited.insert(Pred).second)
+        Worklist.push_back(Pred);
+  }
+
+  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.
@@ -5448,17 +5561,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;
 
@@ -5474,10 +5579,28 @@ 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 ...
+      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();
   }
@@ -5683,11 +5806,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..df0c74e1c7b9e 100644
--- a/llvm/lib/Target/X86/X86InstrInfo.h
+++ b/llvm/lib/Target/X86/X86InstrInfo.h
@@ -83,6 +83,14 @@ 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 +773,24 @@ 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()
+@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(i3...
[truncated]

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

// support on Linux OS.
bool IsWithReloc =
X86EnableAPXForRelocation ? false : isAddMemInstrWithRelocation(MI);
if (IsWithReloc)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (IsWithReloc)
if (!X86EnableAPXForRelocation && isAddMemInstrWithRelocation(MI))

MachineDominatorTree MDT(MF);
MachineDomTreeNode *Node = MDT.getNode(MultiPredMBB);
MachineInstr *Sub = nullptr;
for (Node = Node ? Node->getIDom() : nullptr; Node && !Sub;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!Node)
  return nullptr;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants