#include "SystemZ.h"
#include "SystemZInstrInfo.h"
#include "SystemZTargetMachine.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/CodeGen/LivePhysRegs.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineOperand.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/MC/MCInstrDesc.h"
#include <cassert>
#include <cstdint>
using namespace llvm;
#define DEBUG_TYPE "systemz-elim-compare"
STATISTIC(BranchOnCounts, "Number of branch-on-count instructions");
STATISTIC(LoadAndTraps, "Number of load-and-trap instructions");
STATISTIC(EliminatedComparisons, "Number of eliminated comparisons");
STATISTIC(FusedComparisons, "Number of fused compare-and-branch instructions");
namespace {
struct Reference {
Reference() = default;
Reference &operator|=(const Reference &Other) {
Def |= Other.Def;
Use |= Other.Use;
return *this;
}
explicit operator bool() const { return Def || Use; }
bool Def = false;
bool Use = false;
};
class SystemZElimCompare : public MachineFunctionPass {
public:
static char ID;
SystemZElimCompare() : MachineFunctionPass(ID) {
initializeSystemZElimComparePass(*PassRegistry::getPassRegistry());
}
bool processBlock(MachineBasicBlock &MBB);
bool runOnMachineFunction(MachineFunction &F) override;
MachineFunctionProperties getRequiredProperties() const override {
return MachineFunctionProperties().set(
MachineFunctionProperties::Property::NoVRegs);
}
private:
Reference getRegReferences(MachineInstr &MI, unsigned Reg);
bool convertToBRCT(MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers);
bool convertToLoadAndTrap(MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers);
bool convertToLoadAndTest(MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers);
bool convertToLogical(MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers);
bool adjustCCMasksForInstr(MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers,
unsigned ConvOpc = 0);
bool optimizeCompareZero(MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers);
bool fuseCompareOperations(MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers);
const SystemZInstrInfo *TII = nullptr;
const TargetRegisterInfo *TRI = nullptr;
};
char SystemZElimCompare::ID = 0;
}
INITIALIZE_PASS(SystemZElimCompare, DEBUG_TYPE,
"SystemZ Comparison Elimination", false, false)
static bool preservesValueOf(MachineInstr &MI, unsigned Reg) {
switch (MI.getOpcode()) {
case SystemZ::LR:
case SystemZ::LGR:
case SystemZ::LGFR:
case SystemZ::LTR:
case SystemZ::LTGR:
case SystemZ::LTGFR:
case SystemZ::LER:
case SystemZ::LDR:
case SystemZ::LXR:
case SystemZ::LTEBR:
case SystemZ::LTDBR:
case SystemZ::LTXBR:
if (MI.getOperand(1).getReg() == Reg)
return true;
}
return false;
}
static bool resultTests(MachineInstr &MI, unsigned Reg) {
if (MI.getNumOperands() > 0 && MI.getOperand(0).isReg() &&
MI.getOperand(0).isDef() && MI.getOperand(0).getReg() == Reg)
return true;
return (preservesValueOf(MI, Reg));
}
Reference SystemZElimCompare::getRegReferences(MachineInstr &MI, unsigned Reg) {
Reference Ref;
if (MI.isDebugInstr())
return Ref;
for (const MachineOperand &MO : MI.operands()) {
if (MO.isReg()) {
if (Register MOReg = MO.getReg()) {
if (TRI->regsOverlap(MOReg, Reg)) {
if (MO.isUse())
Ref.Use = true;
else if (MO.isDef())
Ref.Def = true;
}
}
}
}
return Ref;
}
static bool isLoadAndTestAsCmp(MachineInstr &MI) {
return (MI.getOpcode() == SystemZ::LTEBR ||
MI.getOpcode() == SystemZ::LTDBR ||
MI.getOpcode() == SystemZ::LTXBR) &&
MI.getOperand(0).isDead();
}
static unsigned getCompareSourceReg(MachineInstr &Compare) {
unsigned reg = 0;
if (Compare.isCompare())
reg = Compare.getOperand(0).getReg();
else if (isLoadAndTestAsCmp(Compare))
reg = Compare.getOperand(1).getReg();
assert(reg);
return reg;
}
bool SystemZElimCompare::convertToBRCT(
MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers) {
unsigned Opcode = MI.getOpcode();
unsigned BRCT;
if (Opcode == SystemZ::AHI)
BRCT = SystemZ::BRCT;
else if (Opcode == SystemZ::AGHI)
BRCT = SystemZ::BRCTG;
else if (Opcode == SystemZ::AIH)
BRCT = SystemZ::BRCTH;
else
return false;
if (MI.getOperand(2).getImm() != -1)
return false;
if (CCUsers.size() != 1)
return false;
MachineInstr *Branch = CCUsers[0];
if (Branch->getOpcode() != SystemZ::BRC ||
Branch->getOperand(0).getImm() != SystemZ::CCMASK_ICMP ||
Branch->getOperand(1).getImm() != SystemZ::CCMASK_CMP_NE)
return false;
unsigned SrcReg = getCompareSourceReg(Compare);
MachineBasicBlock::iterator MBBI = Compare, MBBE = Branch;
for (++MBBI; MBBI != MBBE; ++MBBI)
if (getRegReferences(*MBBI, SrcReg))
return false;
MachineOperand Target(Branch->getOperand(2));
while (Branch->getNumOperands())
Branch->removeOperand(0);
Branch->setDesc(TII->get(BRCT));
MachineInstrBuilder MIB(*Branch->getParent()->getParent(), Branch);
MIB.add(MI.getOperand(0)).add(MI.getOperand(1)).add(Target);
if (BRCT != SystemZ::BRCTH)
MIB.addReg(SystemZ::CC, RegState::ImplicitDefine | RegState::Dead);
MI.eraseFromParent();
return true;
}
bool SystemZElimCompare::convertToLoadAndTrap(
MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers) {
unsigned LATOpcode = TII->getLoadAndTrap(MI.getOpcode());
if (!LATOpcode)
return false;
if (CCUsers.size() != 1)
return false;
MachineInstr *Branch = CCUsers[0];
if (Branch->getOpcode() != SystemZ::CondTrap ||
Branch->getOperand(0).getImm() != SystemZ::CCMASK_ICMP ||
Branch->getOperand(1).getImm() != SystemZ::CCMASK_CMP_EQ)
return false;
unsigned SrcReg = getCompareSourceReg(Compare);
MachineBasicBlock::iterator MBBI = Compare, MBBE = Branch;
for (++MBBI; MBBI != MBBE; ++MBBI)
if (getRegReferences(*MBBI, SrcReg))
return false;
while (Branch->getNumOperands())
Branch->removeOperand(0);
Branch->setDesc(TII->get(LATOpcode));
MachineInstrBuilder(*Branch->getParent()->getParent(), Branch)
.add(MI.getOperand(0))
.add(MI.getOperand(1))
.add(MI.getOperand(2))
.add(MI.getOperand(3));
MI.eraseFromParent();
return true;
}
bool SystemZElimCompare::convertToLoadAndTest(
MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers) {
unsigned Opcode = TII->getLoadAndTest(MI.getOpcode());
if (!Opcode || !adjustCCMasksForInstr(MI, Compare, CCUsers, Opcode))
return false;
auto MIB = BuildMI(*MI.getParent(), MI, MI.getDebugLoc(), TII->get(Opcode));
for (const auto &MO : MI.operands())
MIB.add(MO);
MIB.setMemRefs(MI.memoperands());
MI.eraseFromParent();
if (!Compare.mayRaiseFPException())
MIB.setMIFlag(MachineInstr::MIFlag::NoFPExcept);
return true;
}
bool SystemZElimCompare::convertToLogical(
MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers) {
unsigned ConvOpc = 0;
switch (MI.getOpcode()) {
case SystemZ::AR: ConvOpc = SystemZ::ALR; break;
case SystemZ::ARK: ConvOpc = SystemZ::ALRK; break;
case SystemZ::AGR: ConvOpc = SystemZ::ALGR; break;
case SystemZ::AGRK: ConvOpc = SystemZ::ALGRK; break;
case SystemZ::A: ConvOpc = SystemZ::AL; break;
case SystemZ::AY: ConvOpc = SystemZ::ALY; break;
case SystemZ::AG: ConvOpc = SystemZ::ALG; break;
default: break;
}
if (!ConvOpc || !adjustCCMasksForInstr(MI, Compare, CCUsers, ConvOpc))
return false;
MI.setDesc(TII->get(ConvOpc));
MI.clearRegisterDeads(SystemZ::CC);
return true;
}
#ifndef NDEBUG
static bool isAddWithImmediate(unsigned Opcode) {
switch(Opcode) {
case SystemZ::AHI:
case SystemZ::AHIK:
case SystemZ::AGHI:
case SystemZ::AGHIK:
case SystemZ::AFI:
case SystemZ::AIH:
case SystemZ::AGFI:
return true;
default: break;
}
return false;
}
#endif
bool SystemZElimCompare::adjustCCMasksForInstr(
MachineInstr &MI, MachineInstr &Compare,
SmallVectorImpl<MachineInstr *> &CCUsers,
unsigned ConvOpc) {
unsigned CompareFlags = Compare.getDesc().TSFlags;
unsigned CompareCCValues = SystemZII::getCCValues(CompareFlags);
int Opcode = (ConvOpc ? ConvOpc : MI.getOpcode());
const MCInstrDesc &Desc = TII->get(Opcode);
unsigned MIFlags = Desc.TSFlags;
if (Compare.mayRaiseFPException()) {
if (ConvOpc && !Desc.mayRaiseFPException())
return false;
if (!ConvOpc && !MI.mayRaiseFPException())
return false;
}
unsigned CCValues = SystemZII::getCCValues(MIFlags);
unsigned ReusableCCMask = CCValues;
if (CompareFlags & SystemZII::IsLogical)
ReusableCCMask &= SystemZ::CCMASK_CMP_EQ;
unsigned OFImplies = 0;
bool LogicalMI = false;
bool MIEquivalentToCmp = false;
if (MI.getFlag(MachineInstr::NoSWrap) &&
(MIFlags & SystemZII::CCIfNoSignedWrap)) {
}
else if ((MIFlags & SystemZII::CCIfNoSignedWrap) &&
MI.getOperand(2).isImm()) {
assert(isAddWithImmediate(Opcode) && "Expected an add with immediate.");
assert(!MI.mayLoadOrStore() && "Expected an immediate term.");
int64_t RHS = MI.getOperand(2).getImm();
if (SystemZ::GRX32BitRegClass.contains(MI.getOperand(0).getReg()) &&
RHS == INT32_MIN)
return false;
OFImplies = (RHS > 0 ? SystemZ::CCMASK_CMP_LT : SystemZ::CCMASK_CMP_GT);
}
else if ((MIFlags & SystemZII::IsLogical) && CCValues) {
LogicalMI = true;
ReusableCCMask = SystemZ::CCMASK_CMP_EQ;
}
else {
ReusableCCMask &= SystemZII::getCompareZeroCCMask(MIFlags);
assert((ReusableCCMask & ~CCValues) == 0 && "Invalid CCValues");
MIEquivalentToCmp =
ReusableCCMask == CCValues && CCValues == CompareCCValues;
}
if (ReusableCCMask == 0)
return false;
if (!MIEquivalentToCmp) {
SmallVector<MachineOperand *, 4> AlterMasks;
for (unsigned int I = 0, E = CCUsers.size(); I != E; ++I) {
MachineInstr *CCUserMI = CCUsers[I];
unsigned Flags = CCUserMI->getDesc().TSFlags;
unsigned FirstOpNum;
if (Flags & SystemZII::CCMaskFirst)
FirstOpNum = 0;
else if (Flags & SystemZII::CCMaskLast)
FirstOpNum = CCUserMI->getNumExplicitOperands() - 2;
else
return false;
unsigned CCValid = CCUserMI->getOperand(FirstOpNum).getImm();
unsigned CCMask = CCUserMI->getOperand(FirstOpNum + 1).getImm();
assert(CCValid == CompareCCValues && (CCMask & ~CCValid) == 0 &&
"Corrupt CC operands of CCUser.");
unsigned OutValid = ~ReusableCCMask & CCValid;
unsigned OutMask = ~ReusableCCMask & CCMask;
if (OutMask != 0 && OutMask != OutValid)
return false;
AlterMasks.push_back(&CCUserMI->getOperand(FirstOpNum));
AlterMasks.push_back(&CCUserMI->getOperand(FirstOpNum + 1));
}
for (unsigned I = 0, E = AlterMasks.size(); I != E; I += 2) {
AlterMasks[I]->setImm(CCValues);
unsigned CCMask = AlterMasks[I + 1]->getImm();
if (LogicalMI) {
CCMask = (CCMask == SystemZ::CCMASK_CMP_EQ ?
SystemZ::CCMASK_LOGICAL_ZERO : SystemZ::CCMASK_LOGICAL_NONZERO);
CCMask &= CCValues; } else {
if (CCMask & ~ReusableCCMask)
CCMask = (CCMask & ReusableCCMask) | (CCValues & ~ReusableCCMask);
CCMask |= (CCMask & OFImplies) ? SystemZ::CCMASK_ARITH_OVERFLOW : 0;
}
AlterMasks[I + 1]->setImm(CCMask);
}
}
if (!ConvOpc)
MI.clearRegisterDeads(SystemZ::CC);
bool BeforeCmp = false;
MachineBasicBlock::iterator MBBI = MI, MBBE = MI.getParent()->end();
for (++MBBI; MBBI != MBBE; ++MBBI)
if (MBBI == Compare) {
BeforeCmp = true;
break;
}
if (BeforeCmp) {
MachineBasicBlock::iterator MBBI = MI, MBBE = Compare;
for (++MBBI; MBBI != MBBE; ++MBBI)
MBBI->clearRegisterKills(SystemZ::CC, TRI);
}
return true;
}
static bool isCompareZero(MachineInstr &Compare) {
switch (Compare.getOpcode()) {
case SystemZ::LTEBRCompare:
case SystemZ::LTDBRCompare:
case SystemZ::LTXBRCompare:
return true;
default:
if (isLoadAndTestAsCmp(Compare))
return true;
return Compare.getNumExplicitOperands() == 2 &&
Compare.getOperand(1).isImm() && Compare.getOperand(1).getImm() == 0;
}
}
bool SystemZElimCompare::optimizeCompareZero(
MachineInstr &Compare, SmallVectorImpl<MachineInstr *> &CCUsers) {
if (!isCompareZero(Compare))
return false;
unsigned SrcReg = getCompareSourceReg(Compare);
MachineBasicBlock &MBB = *Compare.getParent();
Reference CCRefs;
Reference SrcRefs;
for (MachineBasicBlock::reverse_iterator MBBI =
std::next(MachineBasicBlock::reverse_iterator(&Compare)),
MBBE = MBB.rend(); MBBI != MBBE;) {
MachineInstr &MI = *MBBI++;
if (resultTests(MI, SrcReg)) {
if (!CCRefs.Use && !SrcRefs) {
if (convertToBRCT(MI, Compare, CCUsers)) {
BranchOnCounts += 1;
return true;
}
if (convertToLoadAndTrap(MI, Compare, CCUsers)) {
LoadAndTraps += 1;
return true;
}
}
if ((!CCRefs && convertToLoadAndTest(MI, Compare, CCUsers)) ||
(!CCRefs.Def &&
(adjustCCMasksForInstr(MI, Compare, CCUsers) ||
convertToLogical(MI, Compare, CCUsers)))) {
EliminatedComparisons += 1;
return true;
}
}
SrcRefs |= getRegReferences(MI, SrcReg);
if (SrcRefs.Def)
break;
CCRefs |= getRegReferences(MI, SystemZ::CC);
if (CCRefs.Use && CCRefs.Def)
break;
if (Compare.mayRaiseFPException() &&
(MI.isCall() || MI.hasUnmodeledSideEffects()))
break;
}
auto MIRange = llvm::make_range(
std::next(MachineBasicBlock::iterator(&Compare)), MBB.end());
for (MachineInstr &MI : llvm::make_early_inc_range(MIRange)) {
if (preservesValueOf(MI, SrcReg)) {
if (convertToLoadAndTest(MI, Compare, CCUsers)) {
EliminatedComparisons += 1;
return true;
}
}
if (getRegReferences(MI, SrcReg).Def)
return false;
if (getRegReferences(MI, SystemZ::CC))
return false;
}
return false;
}
bool SystemZElimCompare::fuseCompareOperations(
MachineInstr &Compare, SmallVectorImpl<MachineInstr *> &CCUsers) {
if (CCUsers.size() != 1)
return false;
MachineInstr *Branch = CCUsers[0];
SystemZII::FusedCompareType Type;
switch (Branch->getOpcode()) {
case SystemZ::BRC:
Type = SystemZII::CompareAndBranch;
break;
case SystemZ::CondReturn:
Type = SystemZII::CompareAndReturn;
break;
case SystemZ::CallBCR:
Type = SystemZII::CompareAndSibcall;
break;
case SystemZ::CondTrap:
Type = SystemZII::CompareAndTrap;
break;
default:
return false;
}
unsigned FusedOpcode =
TII->getFusedCompare(Compare.getOpcode(), Type, &Compare);
if (!FusedOpcode)
return false;
Register SrcReg = Compare.getOperand(0).getReg();
Register SrcReg2 =
Compare.getOperand(1).isReg() ? Compare.getOperand(1).getReg() : Register();
MachineBasicBlock::iterator MBBI = Compare, MBBE = Branch;
for (++MBBI; MBBI != MBBE; ++MBBI)
if (MBBI->modifiesRegister(SrcReg, TRI) ||
(SrcReg2 && MBBI->modifiesRegister(SrcReg2, TRI)))
return false;
MachineOperand CCMask(MBBI->getOperand(1));
assert((CCMask.getImm() & ~SystemZ::CCMASK_ICMP) == 0 &&
"Invalid condition-code mask for integer comparison");
MachineOperand Target(MBBI->getOperand(
(Type == SystemZII::CompareAndBranch ||
Type == SystemZII::CompareAndSibcall) ? 2 : 0));
const uint32_t *RegMask;
if (Type == SystemZII::CompareAndSibcall)
RegMask = MBBI->getOperand(3).getRegMask();
int CCUse = MBBI->findRegisterUseOperandIdx(SystemZ::CC, false, TRI);
assert(CCUse >= 0 && "BRC/BCR must use CC");
Branch->removeOperand(CCUse);
if (Type == SystemZII::CompareAndSibcall)
Branch->removeOperand(3);
if (Type == SystemZII::CompareAndBranch ||
Type == SystemZII::CompareAndSibcall)
Branch->removeOperand(2);
Branch->removeOperand(1);
Branch->removeOperand(0);
unsigned SrcNOps = 2;
if (FusedOpcode == SystemZ::CLT || FusedOpcode == SystemZ::CLGT)
SrcNOps = 3;
Branch->setDesc(TII->get(FusedOpcode));
MachineInstrBuilder MIB(*Branch->getParent()->getParent(), Branch);
for (unsigned I = 0; I < SrcNOps; I++)
MIB.add(Compare.getOperand(I));
MIB.add(CCMask);
if (Type == SystemZII::CompareAndBranch) {
MIB.add(Target).addReg(SystemZ::CC,
RegState::ImplicitDefine | RegState::Dead);
}
if (Type == SystemZII::CompareAndSibcall) {
MIB.add(Target);
MIB.addRegMask(RegMask);
}
MBBI = Compare;
for (++MBBI; MBBI != MBBE; ++MBBI) {
MBBI->clearRegisterKills(SrcReg, TRI);
if (SrcReg2)
MBBI->clearRegisterKills(SrcReg2, TRI);
}
FusedComparisons += 1;
return true;
}
bool SystemZElimCompare::processBlock(MachineBasicBlock &MBB) {
bool Changed = false;
LivePhysRegs LiveRegs(*TRI);
LiveRegs.addLiveOuts(MBB);
bool CompleteCCUsers = !LiveRegs.contains(SystemZ::CC);
SmallVector<MachineInstr *, 4> CCUsers;
MachineBasicBlock::iterator MBBI = MBB.end();
while (MBBI != MBB.begin()) {
MachineInstr &MI = *--MBBI;
if (CompleteCCUsers && (MI.isCompare() || isLoadAndTestAsCmp(MI)) &&
(optimizeCompareZero(MI, CCUsers) ||
fuseCompareOperations(MI, CCUsers))) {
++MBBI;
MI.eraseFromParent();
Changed = true;
CCUsers.clear();
continue;
}
if (MI.definesRegister(SystemZ::CC)) {
CCUsers.clear();
CompleteCCUsers = true;
}
if (MI.readsRegister(SystemZ::CC) && CompleteCCUsers)
CCUsers.push_back(&MI);
}
return Changed;
}
bool SystemZElimCompare::runOnMachineFunction(MachineFunction &F) {
if (skipFunction(F.getFunction()))
return false;
TII = F.getSubtarget<SystemZSubtarget>().getInstrInfo();
TRI = &TII->getRegisterInfo();
bool Changed = false;
for (auto &MBB : F)
Changed |= processBlock(MBB);
return Changed;
}
FunctionPass *llvm::createSystemZElimComparePass(SystemZTargetMachine &TM) {
return new SystemZElimCompare();
}