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 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..03490671a3130 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,105 @@ static std::pair 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> &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 Visited; + SmallVector 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 +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; @@ -5474,10 +5580,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 +5807,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 Visited; + SmallVector 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> &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(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@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@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 differs 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..a4af76efe8452 --- /dev/null +++ b/llvm/test/CodeGen/X86/apx/optimize-compare-multipred.mir @@ -0,0 +1,183 @@ +# 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. +# (The clobber is rejected by both the idom-chain scan and the subsequent +# path walk; either alone is sufficient to bail.) +--- +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 +... + +# 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 +# verification and live-in walks over a cyclic CFG with a back-edge (the Visited +# sets keep both walks 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 +...