blob: 5364f6e02478695feed7856b071bf37a53fc1671 [file] [edit]
//===- HexagonHVXSaveRemark.cpp - Remark on HVX saves around calls --------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Diagnostic pass that emits optimization remarks when HVX vector registers
// are live across function calls. All HVX registers are caller-saved
// (Section 5.3 of the Hexagon ABI), so every HVX value that is live across a
// call requires a save/restore pair on the stack. Each HVX vector is 64 or
// 128 bytes (depending on the mode), making this overhead expensive. The
// remarks help programmers identify call sites where inlining, hoisting, or
// sinking the call could reduce the save/restore cost.
//
// The pass runs before register allocation while values are still in virtual
// registers. A backward liveness scan over each basic block counts the HVX
// virtual registers (and their corresponding byte cost) live at each call
// instruction.
//
//===----------------------------------------------------------------------===//
#include "HexagonSubtarget.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/InitializePasses.h"
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
using namespace llvm;
#define DEBUG_TYPE "hexagon-hvx-save"
static cl::opt<unsigned> HVXSaveThreshold(
"hexagon-hvx-save-threshold", cl::Hidden, cl::init(128 * 8),
cl::desc("Minimum number of bytes of HVX caller-saved register data live "
"across a call to trigger a remark (default: 8 x 128-byte "
"vectors)"));
namespace {
struct HexagonHVXSaveRemark : public MachineFunctionPass {
static char ID;
HexagonHVXSaveRemark() : MachineFunctionPass(ID) {}
// Returns the number of HVX vectors represented by VReg: 2 for HvxWR
// (vector pair), 1 for HvxVR (single vector), 0 for non-HVX registers.
static unsigned hvxVecCount(Register VReg, const MachineRegisterInfo &MRI) {
const TargetRegisterClass *RC = MRI.getRegClass(VReg);
if (RC == &Hexagon::HvxWRRegClass)
return 2;
if (RC == &Hexagon::HvxVRRegClass)
return 1;
return 0;
}
bool runOnMachineFunction(MachineFunction &MF) override {
auto &MORE = getAnalysis<MachineOptimizationRemarkEmitterPass>().getORE();
if (!MORE.allowExtraAnalysis(DEBUG_TYPE))
return false;
const HexagonSubtarget &HST = MF.getSubtarget<HexagonSubtarget>();
if (!HST.useHVXOps())
return false;
const MachineRegisterInfo &MRI = MF.getRegInfo();
unsigned HVXLen = HST.getVectorLength();
// Compute LiveOut[B] for each block: the set of HVX virtual registers
// that are live on exit from B. We use a standard backward dataflow
// fixed-point:
//
// LiveIn[B] = UEVar[B] union (LiveOut[B] - Def[B])
// LiveOut[B] = union over successors S of LiveIn[S]
//
// where UEVar[B] is the set of HVX vregs that are used in B before any
// definition of that vreg in B (upward-exposed uses), and Def[B] is the
// set of HVX vregs defined in B.
//
// Because MachineBasicBlock::liveins() only contains physical registers,
// we cannot seed cross-block virtual register liveness from successor
// liveins -- we must compute it ourselves.
unsigned NumBlocks = MF.getNumBlockIDs();
using VRegSet = SmallSet<Register, 8>;
// Per-block UEVar and Def sets (HVX vregs only).
SmallVector<VRegSet, 16> UEVar(NumBlocks), BlockDef(NumBlocks);
for (const MachineBasicBlock &MBB : MF) {
unsigned BN = MBB.getNumber();
VRegSet Defs;
for (const MachineInstr &MI : MBB) {
for (const MachineOperand &MO : MI.operands()) {
if (!MO.isReg())
continue;
Register R = MO.getReg();
if (!R.isVirtual() || !hvxVecCount(R, MRI))
continue;
if (MO.isDef()) {
Defs.insert(R);
} else if (MO.isUse() && !Defs.count(R)) {
UEVar[BN].insert(R); // upward-exposed use
}
}
}
BlockDef[BN] = Defs;
}
// LiveOut[B] and LiveIn[B] maps.
SmallVector<VRegSet, 16> LiveOut(NumBlocks), LiveIn(NumBlocks);
// Seed LiveIn from UEVar and iterate until stable.
for (unsigned I = 0; I < NumBlocks; ++I)
LiveIn[I] = UEVar[I];
bool Changed = true;
while (Changed) {
Changed = false;
for (const MachineBasicBlock &MBB : MF) {
unsigned BN = MBB.getNumber();
// LiveOut[B] = union of LiveIn[S] for each successor S.
VRegSet NewLiveOut;
for (const MachineBasicBlock *Succ : MBB.successors())
for (Register R : LiveIn[Succ->getNumber()])
NewLiveOut.insert(R);
if (NewLiveOut != LiveOut[BN]) {
LiveOut[BN] = NewLiveOut;
Changed = true;
}
// LiveIn[B] = UEVar[B] union (LiveOut[B] - Def[B]).
VRegSet NewLiveIn = UEVar[BN];
for (Register R : LiveOut[BN])
if (!BlockDef[BN].count(R))
NewLiveIn.insert(R);
if (NewLiveIn != LiveIn[BN]) {
LiveIn[BN] = NewLiveIn;
Changed = true;
}
}
}
// Now do the backward scan over each block, seeded from LiveOut[B].
for (const MachineBasicBlock &MBB : MF) {
// Backward liveness scan over virtual registers. We track which
// virtual registers are live at each point, then at call instructions
// count those with HVX register classes.
//
// When walking backwards:
// - a def removes a vreg from the live set
// - a use adds a vreg to the live set
// At each call, the live set holds vregs live after the call (i.e., the
// values that must survive across it and therefore need save/restore).
VRegSet LiveVRegs = LiveOut[MBB.getNumber()];
for (const MachineInstr &MI : llvm::reverse(MBB)) {
if (MI.isCall()) {
// Count HVX virtual registers live after (and thus across) this
// call. HvxVR holds one vector (HVXLen bytes); HvxWR holds two
// (2 * HVXLen bytes).
unsigned NumVecs = 0;
for (Register VReg : LiveVRegs)
NumVecs += hvxVecCount(VReg, MRI);
unsigned TotalBytes = NumVecs * HVXLen;
LLVM_DEBUG(dbgs() << "HVXSaveRemark: call in " << MF.getName()
<< " has " << NumVecs << " HVX vector(s) live ("
<< TotalBytes << " bytes)\n");
if (TotalBytes >= HVXSaveThreshold) {
MORE.emit([&]() {
MachineOptimizationRemarkAnalysis R(
DEBUG_TYPE, "HVXSaveAroundCall", MI.getDebugLoc(), &MBB);
R << ore::NV("NumVecs", NumVecs)
<< " HVX caller-saved register(s) ("
<< ore::NV("TotalBytes", TotalBytes)
<< " bytes) live across call";
return R;
});
}
}
// Update liveness: defs kill vregs, uses add them.
for (const MachineOperand &MO : MI.operands()) {
if (!MO.isReg() || !MO.getReg().isVirtual())
continue;
if (MO.isDef())
LiveVRegs.erase(MO.getReg());
else if (MO.isUse())
LiveVRegs.insert(MO.getReg());
}
}
}
return false;
}
StringRef getPassName() const override { return "Hexagon HVX Save Remarks"; }
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<MachineOptimizationRemarkEmitterPass>();
AU.setPreservesAll();
MachineFunctionPass::getAnalysisUsage(AU);
}
};
char HexagonHVXSaveRemark::ID = 0;
} // end anonymous namespace
INITIALIZE_PASS(HexagonHVXSaveRemark, DEBUG_TYPE, "Hexagon HVX Save Remarks",
false, false)
FunctionPass *llvm::createHexagonHVXSaveRemark() {
return new HexagonHVXSaveRemark();
}