//=-- SystemZHazardRecognizer.h - SystemZ Hazard Recognizer -----*- C++ -*-===//
//
// 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 file defines a hazard recognizer for the SystemZ scheduler.
//
// This class is used by the SystemZ scheduling strategy to maintain
// the state during scheduling, and provide cost functions for
// scheduling candidates. This includes:
//
// * Decoder grouping. A decoder group can maximally hold 3 uops, and
// instructions that always begin a new group should be scheduled when
// the current decoder group is empty.
// * Processor resources usage. It is beneficial to balance the use of
// resources.
//
// A goal is to consider all instructions, also those outside of any
// scheduling region. Such instructions are "advanced" past and include
// single instructions before a scheduling region, branches etc.
//
// A block that has only one predecessor continues scheduling with the state
// of it (which may be updated by emitting branches).
//
// ===---------------------------------------------------------------------===//
#include "SystemZHazardRecognizer.h"
#include "llvm/ADT/Statistic.h"
using namespace llvm;
#define DEBUG_TYPE "machine-scheduler"
// This is the limit of processor resource usage at which the
// scheduler should try to look for other instructions (not using the
// critical resource).
static cl::opt<int> ProcResCostLim("procres-cost-lim", cl::Hidden,
cl::desc("The OOO window for processor "
"resources during scheduling."),
cl::init(8));
unsigned SystemZHazardRecognizer::
getNumDecoderSlots(SUnit *SU) const {
const MCSchedClassDesc *SC = getSchedClass(SU);
if (!SC->isValid())
return 0; // IMPLICIT_DEF / KILL -- will not make impact in output.
assert((SC->NumMicroOps != 2 || (SC->BeginGroup && !SC->EndGroup)) &&
"Only cracked instruction can have 2 uops.");
assert((SC->NumMicroOps < 3 || (SC->BeginGroup && SC->EndGroup)) &&
"Expanded instructions always group alone.");
assert((SC->NumMicroOps < 3 || (SC->NumMicroOps % 3 == 0)) &&
"Expanded instructions fill the group(s).");
return SC->NumMicroOps;
}
unsigned SystemZHazardRecognizer::getCurrCycleIdx(SUnit *SU) const {
unsigned Idx = CurrGroupSize;
if (GrpCount % 2)
Idx += 3;
if (SU != nullptr && !fitsIntoCurrentGroup(SU)) {
if (Idx == 1 || Idx == 2)
Idx = 3;
else if (Idx == 4 || Idx == 5)
Idx = 0;
}
return Idx;
}
ScheduleHazardRecognizer::HazardType SystemZHazardRecognizer::
getHazardType(SUnit *SU, int Stalls) {
return (fitsIntoCurrentGroup(SU) ? NoHazard : Hazard);
}
void SystemZHazardRecognizer::Reset() {
CurrGroupSize = 0;
CurrGroupHas4RegOps = false;
clearProcResCounters();
GrpCount = 0;
LastFPdOpCycleIdx = UINT_MAX;
LastEmittedMI = nullptr;
LLVM_DEBUG(CurGroupDbg = "";);
}
bool
SystemZHazardRecognizer::fitsIntoCurrentGroup(SUnit *SU) const {
const MCSchedClassDesc *SC = getSchedClass(SU);
if (!SC->isValid())
return true;
// A cracked instruction only fits into schedule if the current
// group is empty.
if (SC->BeginGroup)
return (CurrGroupSize == 0);
// An instruction with 4 register operands will not fit in last slot.
assert ((CurrGroupSize < 2 || !CurrGroupHas4RegOps) &&
"Current decoder group is already full!");
if (CurrGroupSize == 2 && has4RegOps(SU->getInstr()))
return false;
// Since a full group is handled immediately in EmitInstruction(),
// SU should fit into current group. NumSlots should be 1 or 0,
// since it is not a cracked or expanded instruction.
assert ((getNumDecoderSlots(SU) <= 1) && (CurrGroupSize < 3) &&
"Expected normal instruction to fit in non-full group!");
return true;
}
bool SystemZHazardRecognizer::has4RegOps(const MachineInstr *MI) const {
const MachineFunction &MF = *MI->getParent()->getParent();
const TargetRegisterInfo *TRI = &TII->getRegisterInfo();
const MCInstrDesc &MID = MI->getDesc();
unsigned Count = 0;
for (unsigned OpIdx = 0; OpIdx < MID.getNumOperands(); OpIdx++) {
const TargetRegisterClass *RC = TII->getRegClass(MID, OpIdx, TRI, MF);
if (RC == nullptr)
continue;
if (OpIdx >= MID.getNumDefs() &&
MID.getOperandConstraint(OpIdx, MCOI::TIED_TO) != -1)
continue;
Count++;
}
return Count >= 4;
}
void SystemZHazardRecognizer::nextGroup() {
if (CurrGroupSize == 0)
return;
LLVM_DEBUG(dumpCurrGroup("Completed decode group"));
LLVM_DEBUG(CurGroupDbg = "";);
int NumGroups = ((CurrGroupSize > 3) ? (CurrGroupSize / 3) : 1);
assert((CurrGroupSize <= 3 || CurrGroupSize % 3 == 0) &&
"Current decoder group bad.");
// Reset counter for next group.
CurrGroupSize = 0;
CurrGroupHas4RegOps = false;
GrpCount += ((unsigned) NumGroups);
// Decrease counters for execution units.
for (unsigned i = 0; i < SchedModel->getNumProcResourceKinds(); ++i)
ProcResourceCounters[i] = ((ProcResourceCounters[i] > NumGroups)
? (ProcResourceCounters[i] - NumGroups)
: 0);
// Clear CriticalResourceIdx if it is now below the threshold.
if (CriticalResourceIdx != UINT_MAX &&
(ProcResourceCounters[CriticalResourceIdx] <=
ProcResCostLim))
CriticalResourceIdx = UINT_MAX;
LLVM_DEBUG(dumpState(););
}
#ifndef NDEBUG // Debug output
void SystemZHazardRecognizer::dumpSU(SUnit *SU, raw_ostream &OS) const {
OS << "SU(" << SU->NodeNum << "):";
OS << TII->getName(SU->getInstr()->getOpcode());
const MCSchedClassDesc *SC = getSchedClass(SU);
if (!SC->isValid())
return;
for (TargetSchedModel::ProcResIter
PI = SchedModel->getWriteProcResBegin(SC),
PE = SchedModel->getWriteProcResEnd(SC); PI != PE; ++PI) {
const MCProcResourceDesc &PRD =
*SchedModel->getProcResource(PI->ProcResourceIdx);
std::string FU(PRD.Name);
// trim e.g. Z13_FXaUnit -> FXa
FU = FU.substr(FU.find('_') + 1);
size_t Pos = FU.find("Unit");
if (Pos != std::string::npos)
FU.resize(Pos);
if (FU == "LS") // LSUnit -> LSU
FU = "LSU";
OS << "/" << FU;
if (PI->Cycles > 1)
OS << "(" << PI->Cycles << "cyc)";
}
if (SC->NumMicroOps > 1)
OS << "/" << SC->NumMicroOps << "uops";
if (SC->BeginGroup && SC->EndGroup)
OS << "/GroupsAlone";
else if (SC->BeginGroup)
OS << "/BeginsGroup";
else if (SC->EndGroup)
OS << "/EndsGroup";
if (SU->isUnbuffered)
OS << "/Unbuffered";
if (has4RegOps(SU->getInstr()))
OS << "/4RegOps";
}
void SystemZHazardRecognizer::dumpCurrGroup(std::string Msg) const {
dbgs() << "++ " << Msg;
dbgs() << ": ";
if (CurGroupDbg.empty())
dbgs() << " <empty>\n";
else {
dbgs() << "{ " << CurGroupDbg << " }";
dbgs() << " (" << CurrGroupSize << " decoder slot"
<< (CurrGroupSize > 1 ? "s":"")
<< (CurrGroupHas4RegOps ? ", 4RegOps" : "")
<< ")\n";
}
}
void SystemZHazardRecognizer::dumpProcResourceCounters() const {
bool any = false;
for (unsigned i = 0; i < SchedModel->getNumProcResourceKinds(); ++i)
if (ProcResourceCounters[i] > 0) {
any = true;
break;
}
if (!any)
return;
dbgs() << "++ | Resource counters: ";
for (unsigned i = 0; i < SchedModel->getNumProcResourceKinds(); ++i)
if (ProcResourceCounters[i] > 0)
dbgs() << SchedModel->getProcResource(i)->Name
<< ":" << ProcResourceCounters[i] << " ";
dbgs() << "\n";
if (CriticalResourceIdx != UINT_MAX)
dbgs() << "++ | Critical resource: "
<< SchedModel->getProcResource(CriticalResourceIdx)->Name
<< "\n";
}
void SystemZHazardRecognizer::dumpState() const {
dumpCurrGroup("| Current decoder group");
dbgs() << "++ | Current cycle index: "
<< getCurrCycleIdx() << "\n";
dumpProcResourceCounters();
if (LastFPdOpCycleIdx != UINT_MAX)
dbgs() << "++ | Last FPd cycle index: " << LastFPdOpCycleIdx << "\n";
}
#endif //NDEBUG
void SystemZHazardRecognizer::clearProcResCounters() {
ProcResourceCounters.assign(SchedModel->getNumProcResourceKinds(), 0);
CriticalResourceIdx = UINT_MAX;
}
static inline bool isBranchRetTrap(MachineInstr *MI) {
return (MI->isBranch() || MI->isReturn() ||
MI->getOpcode() == SystemZ::CondTrap);
}
// Update state with SU as the next scheduled unit.
void SystemZHazardRecognizer::
EmitInstruction(SUnit *SU) {
const MCSchedClassDesc *SC = getSchedClass(SU);
LLVM_DEBUG(dbgs() << "++ HazardRecognizer emitting "; dumpSU(SU, dbgs());
dbgs() << "\n";);
LLVM_DEBUG(dumpCurrGroup("Decode group before emission"););
// If scheduling an SU that must begin a new decoder group, move on
// to next group.
if (!fitsIntoCurrentGroup(SU))
nextGroup();
LLVM_DEBUG(raw_string_ostream cgd(CurGroupDbg);
if (CurGroupDbg.length()) cgd << ", "; dumpSU(SU, cgd););
LastEmittedMI = SU->getInstr();
// After returning from a call, we don't know much about the state.
if (SU->isCall) {
LLVM_DEBUG(dbgs() << "++ Clearing state after call.\n";);
Reset();
LastEmittedMI = SU->getInstr();
return;
}
// Increase counter for execution unit(s).
for (TargetSchedModel::ProcResIter
PI = SchedModel->getWriteProcResBegin(SC),
PE = SchedModel->getWriteProcResEnd(SC); PI != PE; ++PI) {
// Don't handle FPd together with the other resources.
if (SchedModel->getProcResource(PI->ProcResourceIdx)->BufferSize == 1)
continue;
int &CurrCounter =
ProcResourceCounters[PI->ProcResourceIdx];
CurrCounter += PI->Cycles;
// Check if this is now the new critical resource.
if ((CurrCounter > ProcResCostLim) &&
(CriticalResourceIdx == UINT_MAX ||
(PI->ProcResourceIdx != CriticalResourceIdx &&
CurrCounter >
ProcResourceCounters[CriticalResourceIdx]))) {
LLVM_DEBUG(
dbgs() << "++ New critical resource: "
<< SchedModel->getProcResource(PI->ProcResourceIdx)->Name
<< "\n";);
CriticalResourceIdx = PI->ProcResourceIdx;
}
}
// Make note of an instruction that uses a blocking resource (FPd).
if (SU->isUnbuffered) {
LastFPdOpCycleIdx = getCurrCycleIdx(SU);
LLVM_DEBUG(dbgs() << "++ Last FPd cycle index: " << LastFPdOpCycleIdx
<< "\n";);
}
// Insert SU into current group by increasing number of slots used
// in current group.
CurrGroupSize += getNumDecoderSlots(SU);
CurrGroupHas4RegOps |= has4RegOps(SU->getInstr());
unsigned GroupLim = (CurrGroupHas4RegOps ? 2 : 3);
assert((CurrGroupSize <= GroupLim || CurrGroupSize == getNumDecoderSlots(SU))
&& "SU does not fit into decoder group!");
// Check if current group is now full/ended. If so, move on to next
// group to be ready to evaluate more candidates.
if (CurrGroupSize >= GroupLim || SC->EndGroup)
nextGroup();
}
int SystemZHazardRecognizer::groupingCost(SUnit *SU) const {
const MCSchedClassDesc *SC = getSchedClass(SU);
if (!SC->isValid())
return 0;
// If SU begins new group, it can either break a current group early
// or fit naturally if current group is empty (negative cost).
if (SC->BeginGroup) {
if (CurrGroupSize)
return 3 - CurrGroupSize;
return -1;
}
// Similarly, a group-ending SU may either fit well (last in group), or
// end the group prematurely.
if (SC->EndGroup) {
unsigned resultingGroupSize =
(CurrGroupSize + getNumDecoderSlots(SU));
if (resultingGroupSize < 3)
return (3 - resultingGroupSize);
return -1;
}
// An instruction with 4 register operands will not fit in last slot.
if (CurrGroupSize == 2 && has4RegOps(SU->getInstr()))
return 1;
// Most instructions can be placed in any decoder slot.
return 0;
}
bool SystemZHazardRecognizer::isFPdOpPreferred_distance(SUnit *SU) const {
assert (SU->isUnbuffered);
// If this is the first FPd op, it should be scheduled high.
if (LastFPdOpCycleIdx == UINT_MAX)
return true;
// If this is not the first PFd op, it should go into the other side
// of the processor to use the other FPd unit there. This should
// generally happen if two FPd ops are placed with 2 other
// instructions between them (modulo 6).
unsigned SUCycleIdx = getCurrCycleIdx(SU);
if (LastFPdOpCycleIdx > SUCycleIdx)
return ((LastFPdOpCycleIdx - SUCycleIdx) == 3);
return ((SUCycleIdx - LastFPdOpCycleIdx) == 3);
}
int SystemZHazardRecognizer::
resourcesCost(SUnit *SU) {
int Cost = 0;
const MCSchedClassDesc *SC = getSchedClass(SU);
if (!SC->isValid())
return 0;
// For a FPd op, either return min or max value as indicated by the
// distance to any prior FPd op.
if (SU->isUnbuffered)
Cost = (isFPdOpPreferred_distance(SU) ? INT_MIN : INT_MAX);
// For other instructions, give a cost to the use of the critical resource.
else if (CriticalResourceIdx != UINT_MAX) {
for (TargetSchedModel::ProcResIter
PI = SchedModel->getWriteProcResBegin(SC),
PE = SchedModel->getWriteProcResEnd(SC); PI != PE; ++PI)
if (PI->ProcResourceIdx == CriticalResourceIdx)
Cost = PI->Cycles;
}
return Cost;
}
void SystemZHazardRecognizer::emitInstruction(MachineInstr *MI,
bool TakenBranch) {
// Make a temporary SUnit.
SUnit SU(MI, 0);
// Set interesting flags.
SU.isCall = MI->isCall();
const MCSchedClassDesc *SC = SchedModel->resolveSchedClass(MI);
for (const MCWriteProcResEntry &PRE :
make_range(SchedModel->getWriteProcResBegin(SC),
SchedModel->getWriteProcResEnd(SC))) {
switch (SchedModel->getProcResource(PRE.ProcResourceIdx)->BufferSize) {
case 0:
SU.hasReservedResource = true;
break;
case 1:
SU.isUnbuffered = true;
break;
default:
break;
}
}
unsigned GroupSizeBeforeEmit = CurrGroupSize;
EmitInstruction(&SU);
if (!TakenBranch && isBranchRetTrap(MI)) {
// NT Branch on second slot ends group.
if (GroupSizeBeforeEmit == 1)
nextGroup();
}
if (TakenBranch && CurrGroupSize > 0)
nextGroup();
assert ((!MI->isTerminator() || isBranchRetTrap(MI)) &&
"Scheduler: unhandled terminator!");
}
void SystemZHazardRecognizer::
copyState(SystemZHazardRecognizer *Incoming) {
// Current decoder group
CurrGroupSize = Incoming->CurrGroupSize;
LLVM_DEBUG(CurGroupDbg = Incoming->CurGroupDbg;);
// Processor resources
ProcResourceCounters = Incoming->ProcResourceCounters;
CriticalResourceIdx = Incoming->CriticalResourceIdx;
// FPd
LastFPdOpCycleIdx = Incoming->LastFPdOpCycleIdx;
GrpCount = Incoming->GrpCount;
}