blob: 35ccd97b1b667ae4b24de117e8d3b1d30d149442 [file] [edit]
//===---- LoongArchMemoryBarrierOpt.cpp - Memory barrier Optimization -----===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// This pass removes or merges redundant memory barrier instructions.
///
/// - DBAR x + DBAR y -> DBAR (x & y)
/// - DBAR x + AMO_DB -> AMO_DB
/// - DBAR x + AMO -> AMO_DB
/// - DBAR x + LL -> LL
/// - AMO_DB + DBAR x -> AMO_DB
/// - AMO + DBAR x -> AMO_DB
/// - SC + DBAR x -> SC
///
//===----------------------------------------------------------------------===//
#include "LoongArch.h"
#include "LoongArchInstrInfo.h"
#include "LoongArchSubtarget.h"
#include "llvm/CodeGen/MachineDominators.h"
#include "llvm/CodeGen/MachinePostDominators.h"
#include "llvm/InitializePasses.h"
using namespace llvm;
#define DEBUG_TYPE "loongarch-memory-barrier-opt"
#define LOONGARCH_MEMORY_BARRIER_OPT_NAME \
"LoongArch Memory Barrier Optimisation pass"
static cl::opt<bool> RequireNoPathBypass(
"loongarch-require-no-path-bypass",
cl::desc("Optimize only when no paths bypass either memory barrier"),
cl::init(true), cl::Hidden);
static cl::opt<bool> MergeAMOWithMB(
"loongarch-merge-amo-with-dbar",
cl::desc("Merge AMOs with DBARs into AMO_DB during optimization"),
cl::init(true), cl::Hidden);
static cl::opt<bool> ReplaceEliminatedMBToNop(
"loongarch-replace-eliminated-dbar-to-nop",
cl::desc("Replace eliminated DBARs with NOPs to preserve code layout"),
cl::init(false), cl::Hidden);
namespace {
#define AMO_CASES \
CASE(AMSWAP, B) \
CASE(AMSWAP, H) \
CASE(AMSWAP, W) \
CASE(AMSWAP, D) \
CASE(AMADD, B) \
CASE(AMADD, H) \
CASE(AMADD, W) \
CASE(AMADD, D) \
CASE(AMAND, W) \
CASE(AMAND, D) \
CASE(AMOR, W) \
CASE(AMOR, D) \
CASE(AMXOR, W) \
CASE(AMXOR, D) \
CASE(AMMAX, W) \
CASE(AMMAX, D) \
CASE(AMMAX, WU) \
CASE(AMMAX, DU) \
CASE(AMMIN, W) \
CASE(AMMIN, D) \
CASE(AMMIN, WU) \
CASE(AMMIN, DU) \
CASE(AMCAS, B) \
CASE(AMCAS, H) \
CASE(AMCAS, W) \
CASE(AMCAS, D)
static bool isMB(const MachineInstr &MI) {
return MI.getOpcode() == LoongArch::DBAR;
}
static bool isLL(const MachineInstr &MI) {
switch (MI.getOpcode()) {
case LoongArch::LL_W:
case LoongArch::LL_D:
return true;
default:
return false;
}
}
static bool isSC(const MachineInstr &MI) {
switch (MI.getOpcode()) {
case LoongArch::SC_W:
case LoongArch::SC_D:
case LoongArch::SC_Q:
return true;
default:
return false;
}
}
static std::optional<unsigned> isAM(const MachineInstr &MI) {
#define CASE(Name, Suffix) \
case LoongArch::Name##_##Suffix: \
if (!MergeAMOWithMB) \
return std::nullopt; \
[[fallthrough]]; \
case LoongArch::Name##__DB_##Suffix: \
return LoongArch::Name##__DB_##Suffix;
switch (MI.getOpcode()) {
AMO_CASES
default:
return std::nullopt;
}
#undef CASE
}
static bool isSafeToSkip(const MachineInstr &MI) {
if (MI.mayLoadOrStore())
return false;
if (MI.isCall() || MI.isReturn())
return false;
if (MI.isInlineAsm())
return false;
if (MI.hasUnmodeledSideEffects())
return isMB(MI);
return true;
}
struct BarrierHint {
BarrierHint(unsigned Hint) : Hint(Hint) {}
bool subsumes(const BarrierHint &O) const { return (Hint & O.Hint) == Hint; }
BarrierHint merge(const BarrierHint &O) const {
return BarrierHint(Hint & O.Hint);
}
static inline bool isValid(unsigned Hint) { return (Hint & ~0x1f) == 0; }
unsigned Hint;
};
struct InstBarrier {
InstBarrier(MachineInstr &MI)
: MI(&MI), Pre(0), Post(0), OpcAMDB(0), IsMB(false), IsAM(false) {
if (isMB(MI)) {
unsigned Hint = MI.getOperand(0).getImm();
if (!BarrierHint::isValid(Hint))
return;
IsMB = true;
Pre = Post = BarrierHint(Hint);
} else if (isLL(MI)) {
IsAM = true;
Pre = BarrierHint(0b10000);
Post = BarrierHint(0b11111);
} else if (isSC(MI)) {
IsAM = true;
Pre = BarrierHint(0b11111);
Post = BarrierHint(0b10000);
} else if (auto R = isAM(MI)) {
IsAM = true;
OpcAMDB = *R;
Pre = Post = BarrierHint(0b10000);
}
}
MachineInstr *MI;
BarrierHint Pre;
BarrierHint Post;
StringRef OpName;
StringRef Operands;
unsigned OpcAMDB;
bool IsMB;
bool IsAM;
};
class LoongArchMemoryBarrierOpt : public MachineFunctionPass {
public:
static char ID;
LoongArchMemoryBarrierOpt() : MachineFunctionPass(ID) {}
StringRef getPassName() const override {
return LOONGARCH_MEMORY_BARRIER_OPT_NAME;
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<MachineDominatorTreeWrapperPass>();
AU.addPreserved<MachineDominatorTreeWrapperPass>();
AU.addRequired<MachinePostDominatorTreeWrapperPass>();
AU.addPreserved<MachinePostDominatorTreeWrapperPass>();
MachineFunctionPass::getAnalysisUsage(AU);
}
bool runOnMachineFunction(MachineFunction &Fn) override;
private:
enum : unsigned {
CandidateA = 1u << 0,
CandidateB = 1u << 1,
};
unsigned resolveBarrierRedundancy(const MachineInstr *A,
const MachineInstr *B) const;
bool eliminateRedundantBarrier(InstBarrier &IA, InstBarrier &IB) const;
MachineFunction *MF;
const MachineDominatorTree *MDT;
const MachinePostDominatorTree *MPDT;
};
static bool checkAllPathSafe(const MachineBasicBlock *MBBA,
const MachineBasicBlock *MBBB, bool IsAToB) {
const MachineBasicBlock *Start = IsAToB ? MBBA : MBBB;
const MachineBasicBlock *End = IsAToB ? MBBB : MBBA;
SmallVector<const MachineBasicBlock *, 16> Worklist;
DenseSet<const MachineBasicBlock *> Visited;
Worklist.push_back(Start);
Visited.insert(Start);
while (!Worklist.empty()) {
const MachineBasicBlock *BB = Worklist.pop_back_val();
if (BB == End)
continue;
if (BB != Start) {
for (const MachineInstr &MI : *BB) {
if (!isSafeToSkip(MI))
return false;
}
}
if (IsAToB) {
for (const MachineBasicBlock *Succ : BB->successors()) {
if (Visited.insert(Succ).second)
Worklist.push_back(Succ);
}
} else {
for (const MachineBasicBlock *Pred : BB->predecessors()) {
if (Visited.insert(Pred).second)
Worklist.push_back(Pred);
}
}
}
return true;
}
// Returns a bitmask indicating removal candidates: A (bit 1) and B (bit 2).
unsigned LoongArchMemoryBarrierOpt::resolveBarrierRedundancy(
const MachineInstr *A, const MachineInstr *B) const {
const MachineBasicBlock *MBBA = A->getParent();
const MachineBasicBlock *MBBB = B->getParent();
if (MBBA == MBBB) {
/* A -> B */
for (auto It = std::next(A->getIterator()); It != MBBA->end(); ++It) {
if (It == B->getIterator())
return CandidateA | CandidateB;
if (!isSafeToSkip(*It))
return 0;
}
return 0;
}
// Cross-block walk
bool ADomB = MDT->dominates(MBBA, MBBB);
bool BPostDomA = MPDT->dominates(MBBB, MBBA);
unsigned Mask = 0;
if (!ADomB && !BPostDomA)
return 0;
/* A -> MBBA->end() */
for (auto It = std::next(A->getIterator()); It != MBBA->end(); ++It)
if (!isSafeToSkip(*It))
return 0;
/* B -> MBBB->begin() */
for (auto It = MBBB->begin(); It != B->getIterator(); ++It)
if (!isSafeToSkip(*It))
return 0;
/* MBBA -> MBBB */
if (BPostDomA)
if (checkAllPathSafe(MBBA, MBBB, true /*IsAToB*/))
Mask |= CandidateA;
/* MBBB -> MBBA */
if (ADomB)
if (checkAllPathSafe(MBBA, MBBB, false /*IsAToB*/))
Mask |= CandidateB;
return Mask;
}
// Update DBAR hint
static void updateMB(InstBarrier &I, BarrierHint Hint, MachineFunction *MF) {
assert(I.IsMB && "Unexpected!");
I.Pre = I.Post = Hint;
I.MI->getOperand(0).setImm(Hint.Hint);
}
// Replace AMO to AMO_DB
static void replaceAM(InstBarrier &I, MachineFunction *MF) {
if (!I.IsAM)
return;
if (I.OpcAMDB) {
auto &ST = MF->getSubtarget<LoongArchSubtarget>();
I.MI->setDesc(ST.getInstrInfo()->get(I.OpcAMDB));
}
}
bool LoongArchMemoryBarrierOpt::eliminateRedundantBarrier(
InstBarrier &IA, InstBarrier &IB) const {
MachineInstr *A = IA.MI;
MachineInstr *B = IB.MI;
if (!A || !B)
return false; // Already erased
if (A == B)
return false;
unsigned Mask = resolveBarrierRedundancy(A, B);
if (!Mask)
return false;
auto eraseOrReplaceWithNop = [&](MachineInstr *MI) {
if (ReplaceEliminatedMBToNop) {
auto &ST = MF->getSubtarget<LoongArchSubtarget>();
BuildMI(*MI->getParent(), MI->getIterator(), MI->getDebugLoc(),
ST.getInstrInfo()->get(LoongArch::ANDI), LoongArch::R0)
.addReg(LoongArch::R0)
.addImm(0);
}
MI->eraseFromParent();
};
// A B
// DBAR x + DBAR y -> DBAR (x & y)
// DBAR x + AMO_DB -> AMO_DB
// DBAR x + AMO -> AMO_DB
// DBAR x + LL -> LL
if ((Mask & CandidateA) && IA.IsMB) {
if (!IB.Pre.subsumes(IA.Post)) {
if (!IB.IsMB || (RequireNoPathBypass && !(Mask & CandidateB)))
return false;
updateMB(IB, IB.Pre.merge(IA.Post), MF);
}
replaceAM(IB, MF);
eraseOrReplaceWithNop(A);
IA.MI = nullptr;
return true;
}
// A B
// DBAR x + DBAR y -> DBAR (x & y)
// AMO_DB + DBAR x -> AMO_DB
// AMO + DBAR x -> AMO_DB
// SC + DBAR x -> SC
if ((Mask & CandidateB) && IB.IsMB) {
if (!IA.Post.subsumes(IB.Pre)) {
if (!IA.IsMB || (RequireNoPathBypass && !(Mask & CandidateA)))
return false;
updateMB(IA, IA.Post.merge(IB.Pre), MF);
}
replaceAM(IA, MF);
eraseOrReplaceWithNop(B);
IB.MI = nullptr;
return true;
}
return false;
}
bool LoongArchMemoryBarrierOpt::runOnMachineFunction(MachineFunction &Fn) {
if (skipFunction(Fn.getFunction()))
return false;
MF = &Fn;
MDT = &getAnalysis<MachineDominatorTreeWrapperPass>().getDomTree();
MPDT = &getAnalysis<MachinePostDominatorTreeWrapperPass>().getPostDomTree();
SmallVector<InstBarrier, 32> Sites;
bool Changed = false;
for (MachineBasicBlock &MBB : Fn)
for (MachineInstr &MI : MBB) {
InstBarrier IB(MI);
if (IB.IsMB || IB.IsAM)
Sites.push_back(IB);
}
for (size_t a = 0; a < Sites.size(); ++a) {
for (size_t b = a + 1; b < Sites.size(); ++b) {
InstBarrier &IA = Sites[a];
InstBarrier &IB = Sites[b];
Changed |= eliminateRedundantBarrier(IA, IB);
Changed |= eliminateRedundantBarrier(IB, IA);
}
}
return Changed;
}
} // namespace
char LoongArchMemoryBarrierOpt::ID = 0;
INITIALIZE_PASS_BEGIN(LoongArchMemoryBarrierOpt, DEBUG_TYPE,
LOONGARCH_MEMORY_BARRIER_OPT_NAME, false, false)
INITIALIZE_PASS_DEPENDENCY(MachineDominatorTreeWrapperPass)
INITIALIZE_PASS_DEPENDENCY(MachinePostDominatorTreeWrapperPass)
INITIALIZE_PASS_END(LoongArchMemoryBarrierOpt, DEBUG_TYPE,
LOONGARCH_MEMORY_BARRIER_OPT_NAME, false, false)
FunctionPass *llvm::createLoongArchMemoryBarrierOptPass() {
return new LoongArchMemoryBarrierOpt();
}