[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