blob: 232a7be2a9f5fc3a92a285cac5d5787b50d97789 [file] [log] [blame]
//===- ARCOptAddrMode.cpp ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
/// \file
/// This pass folds LD/ST + ADD pairs into Pre/Post-increment form of
/// load/store instructions.
//===----------------------------------------------------------------------===//
#include "ARC.h"
#define GET_INSTRMAP_INFO
#include "ARCInstrInfo.h"
#include "ARCTargetMachine.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineDominators.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/InitializePasses.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
#define OPTADDRMODE_DESC "ARC load/store address mode"
#define OPTADDRMODE_NAME "arc-addr-mode"
#define DEBUG_TYPE "arc-addr-mode"
namespace llvm {
FunctionPass *createARCOptAddrMode();
void initializeARCOptAddrModePass(PassRegistry &);
} // end namespace llvm
namespace {
class ARCOptAddrMode : public MachineFunctionPass {
public:
static char ID;
ARCOptAddrMode() : MachineFunctionPass(ID) {}
StringRef getPassName() const override { return OPTADDRMODE_DESC; }
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesCFG();
MachineFunctionPass::getAnalysisUsage(AU);
AU.addRequired<MachineDominatorTree>();
AU.addPreserved<MachineDominatorTree>();
}
bool runOnMachineFunction(MachineFunction &MF) override;
private:
const ARCSubtarget *AST = nullptr;
const ARCInstrInfo *AII = nullptr;
MachineRegisterInfo *MRI = nullptr;
MachineDominatorTree *MDT = nullptr;
// Tries to combine \p Ldst with increment of its base register to form
// single post-increment instruction.
MachineInstr *tryToCombine(MachineInstr &Ldst);
// Returns true if result of \p Add is not used before \p Ldst
bool noUseOfAddBeforeLoadOrStore(const MachineInstr *Add,
const MachineInstr *Ldst);
// Returns true if load/store instruction \p Ldst can be hoisted up to
// instruction \p To
bool canHoistLoadStoreTo(MachineInstr *Ldst, MachineInstr *To);
// Returns true if load/store instruction \p Ldst can be sunk down
// to instruction \p To
bool canSinkLoadStoreTo(MachineInstr *Ldst, MachineInstr *To);
// Check if instructions \p Ldst and \p Add can be moved to become adjacent
// If they can return instruction which need not to move.
// If \p Uses is not null, fill it with instructions after \p Ldst which use
// \p Ldst's base register
MachineInstr *canJoinInstructions(MachineInstr *Ldst, MachineInstr *Add,
SmallVectorImpl<MachineInstr *> *Uses);
// Returns true if all instruction in \p Uses array can be adjusted
// to accomodate increment of register \p BaseReg by \p Incr
bool canFixPastUses(const ArrayRef<MachineInstr *> &Uses,
MachineOperand &Incr, unsigned BaseReg);
// Update all instructions in \p Uses to accomodate increment
// of \p BaseReg by \p Offset
void fixPastUses(ArrayRef<MachineInstr *> Uses, unsigned BaseReg,
int64_t Offset);
// Change instruction \p Ldst to postincrement form.
// \p NewBase is register to hold update base value
// \p NewOffset is instruction's new offset
void changeToAddrMode(MachineInstr &Ldst, unsigned NewOpcode,
unsigned NewBase, MachineOperand &NewOffset);
bool processBasicBlock(MachineBasicBlock &MBB);
};
} // end anonymous namespace
char ARCOptAddrMode::ID = 0;
INITIALIZE_PASS_BEGIN(ARCOptAddrMode, OPTADDRMODE_NAME, OPTADDRMODE_DESC, false,
false)
INITIALIZE_PASS_DEPENDENCY(MachineDominatorTree)
INITIALIZE_PASS_END(ARCOptAddrMode, OPTADDRMODE_NAME, OPTADDRMODE_DESC, false,
false)
// Return true if \p Off can be used as immediate offset
// operand of load/store instruction (S9 literal)
static bool isValidLoadStoreOffset(int64_t Off) { return isInt<9>(Off); }
// Return true if \p Off can be used as immediate operand of
// ADD/SUB instruction (U6 literal)
static bool isValidIncrementOffset(int64_t Off) { return isUInt<6>(Off); }
static bool isAddConstantOp(const MachineInstr &MI, int64_t &Amount) {
int64_t Sign = 1;
switch (MI.getOpcode()) {
case ARC::SUB_rru6:
Sign = -1;
LLVM_FALLTHROUGH;
case ARC::ADD_rru6:
assert(MI.getOperand(2).isImm() && "Expected immediate operand");
Amount = Sign * MI.getOperand(2).getImm();
return true;
default:
return false;
}
}
// Return true if \p MI dominates of uses of virtual register \p VReg
static bool dominatesAllUsesOf(const MachineInstr *MI, unsigned VReg,
MachineDominatorTree *MDT,
MachineRegisterInfo *MRI) {
assert(Register::isVirtualRegister(VReg) && "Expected virtual register!");
for (auto it = MRI->use_nodbg_begin(VReg), end = MRI->use_nodbg_end();
it != end; ++it) {
MachineInstr *User = it->getParent();
if (User->isPHI()) {
unsigned BBOperandIdx = User->getOperandNo(&*it) + 1;
MachineBasicBlock *MBB = User->getOperand(BBOperandIdx).getMBB();
if (MBB->empty()) {
const MachineBasicBlock *InstBB = MI->getParent();
assert(InstBB != MBB && "Instruction found in empty MBB");
if (!MDT->dominates(InstBB, MBB))
return false;
continue;
}
User = &*MBB->rbegin();
}
if (!MDT->dominates(MI, User))
return false;
}
return true;
}
// Return true if \p MI is load/store instruction with immediate offset
// which can be adjusted by \p Disp
static bool isLoadStoreThatCanHandleDisplacement(const TargetInstrInfo *TII,
const MachineInstr &MI,
int64_t Disp) {
unsigned BasePos, OffPos;
if (!TII->getBaseAndOffsetPosition(MI, BasePos, OffPos))
return false;
const MachineOperand &MO = MI.getOperand(OffPos);
if (!MO.isImm())
return false;
int64_t Offset = MO.getImm() + Disp;
return isValidLoadStoreOffset(Offset);
}
bool ARCOptAddrMode::noUseOfAddBeforeLoadOrStore(const MachineInstr *Add,
const MachineInstr *Ldst) {
Register R = Add->getOperand(0).getReg();
return dominatesAllUsesOf(Ldst, R, MDT, MRI);
}
MachineInstr *ARCOptAddrMode::tryToCombine(MachineInstr &Ldst) {
assert(Ldst.mayLoadOrStore() && "LD/ST instruction expected");
unsigned BasePos, OffsetPos;
LLVM_DEBUG(dbgs() << "[ABAW] tryToCombine " << Ldst);
if (!AII->getBaseAndOffsetPosition(Ldst, BasePos, OffsetPos)) {
LLVM_DEBUG(dbgs() << "[ABAW] Not a recognized load/store\n");
return nullptr;
}
MachineOperand &Base = Ldst.getOperand(BasePos);
MachineOperand &Offset = Ldst.getOperand(OffsetPos);
assert(Base.isReg() && "Base operand must be register");
if (!Offset.isImm()) {
LLVM_DEBUG(dbgs() << "[ABAW] Offset is not immediate\n");
return nullptr;
}
Register B = Base.getReg();
if (Register::isStackSlot(B) || !Register::isVirtualRegister(B)) {
LLVM_DEBUG(dbgs() << "[ABAW] Base is not VReg\n");
return nullptr;
}
// TODO: try to generate address preincrement
if (Offset.getImm() != 0) {
LLVM_DEBUG(dbgs() << "[ABAW] Non-zero offset\n");
return nullptr;
}
for (auto &Add : MRI->use_nodbg_instructions(B)) {
int64_t Incr;
if (!isAddConstantOp(Add, Incr))
continue;
if (!isValidLoadStoreOffset(Incr))
continue;
SmallVector<MachineInstr *, 8> Uses;
MachineInstr *MoveTo = canJoinInstructions(&Ldst, &Add, &Uses);
if (!MoveTo)
continue;
if (!canFixPastUses(Uses, Add.getOperand(2), B))
continue;
LLVM_DEBUG(MachineInstr *First = &Ldst; MachineInstr *Last = &Add;
if (MDT->dominates(Last, First)) std::swap(First, Last);
dbgs() << "[ABAW] Instructions " << *First << " and " << *Last
<< " combined\n";
);
MachineInstr *Result = Ldst.getNextNode();
if (MoveTo == &Add) {
Ldst.removeFromParent();
Add.getParent()->insertAfter(Add.getIterator(), &Ldst);
}
if (Result == &Add)
Result = Result->getNextNode();
fixPastUses(Uses, B, Incr);
int NewOpcode = ARC::getPostIncOpcode(Ldst.getOpcode());
assert(NewOpcode > 0 && "No postincrement form found");
unsigned NewBaseReg = Add.getOperand(0).getReg();
changeToAddrMode(Ldst, NewOpcode, NewBaseReg, Add.getOperand(2));
Add.eraseFromParent();
return Result;
}
return nullptr;
}
MachineInstr *
ARCOptAddrMode::canJoinInstructions(MachineInstr *Ldst, MachineInstr *Add,
SmallVectorImpl<MachineInstr *> *Uses) {
assert(Ldst && Add && "NULL instruction passed");
MachineInstr *First = Add;
MachineInstr *Last = Ldst;
if (MDT->dominates(Ldst, Add))
std::swap(First, Last);
else if (!MDT->dominates(Add, Ldst))
return nullptr;
LLVM_DEBUG(dbgs() << "canJoinInstructions: " << *First << *Last);
unsigned BasePos, OffPos;
if (!AII->getBaseAndOffsetPosition(*Ldst, BasePos, OffPos)) {
LLVM_DEBUG(
dbgs()
<< "[canJoinInstructions] Cannot determine base/offset position\n");
return nullptr;
}
Register BaseReg = Ldst->getOperand(BasePos).getReg();
// prohibit this:
// v1 = add v0, c
// st v1, [v0, 0]
// and this
// st v0, [v0, 0]
// v1 = add v0, c
if (Ldst->mayStore() && Ldst->getOperand(0).isReg()) {
Register StReg = Ldst->getOperand(0).getReg();
if (Add->getOperand(0).getReg() == StReg || BaseReg == StReg) {
LLVM_DEBUG(dbgs() << "[canJoinInstructions] Store uses result of Add\n");
return nullptr;
}
}
SmallVector<MachineInstr *, 4> UsesAfterLdst;
SmallVector<MachineInstr *, 4> UsesAfterAdd;
for (MachineInstr &MI : MRI->use_nodbg_instructions(BaseReg)) {
if (&MI == Ldst || &MI == Add)
continue;
if (&MI != Add && MDT->dominates(Ldst, &MI))
UsesAfterLdst.push_back(&MI);
else if (!MDT->dominates(&MI, Ldst))
return nullptr;
if (MDT->dominates(Add, &MI))
UsesAfterAdd.push_back(&MI);
}
MachineInstr *Result = nullptr;
if (First == Add) {
// n = add b, i
// ...
// x = ld [b, o] or x = ld [n, o]
if (noUseOfAddBeforeLoadOrStore(First, Last)) {
Result = Last;
LLVM_DEBUG(dbgs() << "[canJoinInstructions] Can sink Add down to Ldst\n");
} else if (canHoistLoadStoreTo(Ldst, Add)) {
Result = First;
LLVM_DEBUG(dbgs() << "[canJoinInstructions] Can hoist Ldst to Add\n");
}
} else {
// x = ld [b, o]
// ...
// n = add b, i
Result = First;
LLVM_DEBUG(dbgs() << "[canJoinInstructions] Can hoist Add to Ldst\n");
}
if (Result && Uses)
*Uses = (Result == Ldst) ? UsesAfterLdst : UsesAfterAdd;
return Result;
}
bool ARCOptAddrMode::canFixPastUses(const ArrayRef<MachineInstr *> &Uses,
MachineOperand &Incr, unsigned BaseReg) {
assert(Incr.isImm() && "Expected immediate increment");
int64_t NewOffset = Incr.getImm();
for (MachineInstr *MI : Uses) {
int64_t Dummy;
if (isAddConstantOp(*MI, Dummy)) {
if (isValidIncrementOffset(Dummy + NewOffset))
continue;
return false;
}
if (isLoadStoreThatCanHandleDisplacement(AII, *MI, -NewOffset))
continue;
LLVM_DEBUG(dbgs() << "Instruction cannot handle displacement " << -NewOffset
<< ": " << *MI);
return false;
}
return true;
}
void ARCOptAddrMode::fixPastUses(ArrayRef<MachineInstr *> Uses,
unsigned NewBase, int64_t NewOffset) {
for (MachineInstr *MI : Uses) {
int64_t Amount;
unsigned BasePos, OffPos;
if (isAddConstantOp(*MI, Amount)) {
NewOffset += Amount;
assert(isValidIncrementOffset(NewOffset) &&
"New offset won't fit into ADD instr");
BasePos = 1;
OffPos = 2;
} else if (AII->getBaseAndOffsetPosition(*MI, BasePos, OffPos)) {
MachineOperand &MO = MI->getOperand(OffPos);
assert(MO.isImm() && "expected immediate operand");
NewOffset += MO.getImm();
assert(isValidLoadStoreOffset(NewOffset) &&
"New offset won't fit into LD/ST");
} else
llvm_unreachable("unexpected instruction");
MI->getOperand(BasePos).setReg(NewBase);
MI->getOperand(OffPos).setImm(NewOffset);
}
}
bool ARCOptAddrMode::canHoistLoadStoreTo(MachineInstr *Ldst, MachineInstr *To) {
if (Ldst->getParent() != To->getParent())
return false;
MachineBasicBlock::const_iterator MI(To), ME(Ldst),
End(Ldst->getParent()->end());
bool IsStore = Ldst->mayStore();
for (; MI != ME && MI != End; ++MI) {
if (MI->isDebugValue())
continue;
if (MI->mayStore() || MI->isCall() || MI->isInlineAsm() ||
MI->hasUnmodeledSideEffects())
return false;
if (IsStore && MI->mayLoad())
return false;
}
for (auto &O : Ldst->explicit_operands()) {
if (!O.isReg() || !O.isUse())
continue;
MachineInstr *OpDef = MRI->getVRegDef(O.getReg());
if (!OpDef || !MDT->dominates(OpDef, To))
return false;
}
return true;
}
bool ARCOptAddrMode::canSinkLoadStoreTo(MachineInstr *Ldst, MachineInstr *To) {
// Can only sink load/store within same BB
if (Ldst->getParent() != To->getParent())
return false;
MachineBasicBlock::const_iterator MI(Ldst), ME(To),
End(Ldst->getParent()->end());
bool IsStore = Ldst->mayStore();
bool IsLoad = Ldst->mayLoad();
Register ValReg = IsLoad ? Ldst->getOperand(0).getReg() : Register();
for (; MI != ME && MI != End; ++MI) {
if (MI->isDebugValue())
continue;
if (MI->mayStore() || MI->isCall() || MI->isInlineAsm() ||
MI->hasUnmodeledSideEffects())
return false;
if (IsStore && MI->mayLoad())
return false;
if (ValReg && MI->readsVirtualRegister(ValReg))
return false;
}
return true;
}
void ARCOptAddrMode::changeToAddrMode(MachineInstr &Ldst, unsigned NewOpcode,
unsigned NewBase,
MachineOperand &NewOffset) {
bool IsStore = Ldst.mayStore();
unsigned BasePos, OffPos;
MachineOperand Src = MachineOperand::CreateImm(0xDEADBEEF);
AII->getBaseAndOffsetPosition(Ldst, BasePos, OffPos);
Register BaseReg = Ldst.getOperand(BasePos).getReg();
Ldst.RemoveOperand(OffPos);
Ldst.RemoveOperand(BasePos);
if (IsStore) {
Src = Ldst.getOperand(BasePos - 1);
Ldst.RemoveOperand(BasePos - 1);
}
Ldst.setDesc(AST->getInstrInfo()->get(NewOpcode));
Ldst.addOperand(MachineOperand::CreateReg(NewBase, true));
if (IsStore)
Ldst.addOperand(Src);
Ldst.addOperand(MachineOperand::CreateReg(BaseReg, false));
Ldst.addOperand(NewOffset);
LLVM_DEBUG(dbgs() << "[ABAW] New Ldst: " << Ldst);
}
bool ARCOptAddrMode::processBasicBlock(MachineBasicBlock &MBB) {
bool Changed = false;
for (auto MI = MBB.begin(), ME = MBB.end(); MI != ME; ++MI) {
if (MI->isDebugValue())
continue;
if (!MI->mayLoad() && !MI->mayStore())
continue;
if (ARC::getPostIncOpcode(MI->getOpcode()) < 0)
continue;
MachineInstr *Res = tryToCombine(*MI);
if (Res) {
Changed = true;
// Res points to the next instruction. Rewind to process it
MI = std::prev(Res->getIterator());
}
}
return Changed;
}
bool ARCOptAddrMode::runOnMachineFunction(MachineFunction &MF) {
if (skipFunction(MF.getFunction()))
return false;
AST = &MF.getSubtarget<ARCSubtarget>();
AII = AST->getInstrInfo();
MRI = &MF.getRegInfo();
MDT = &getAnalysis<MachineDominatorTree>();
bool Changed = false;
for (auto &MBB : MF)
Changed |= processBasicBlock(MBB);
return Changed;
}
//===----------------------------------------------------------------------===//
// Public Constructor Functions
//===----------------------------------------------------------------------===//
FunctionPass *llvm::createARCOptAddrMode() { return new ARCOptAddrMode(); }