aboutsummaryrefslogtreecommitdiff
path: root/llvm/lib/Target/AMDGPU/GCNSchedStrategy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'llvm/lib/Target/AMDGPU/GCNSchedStrategy.cpp')
-rw-r--r--llvm/lib/Target/AMDGPU/GCNSchedStrategy.cpp835
1 files changed, 527 insertions, 308 deletions
diff --git a/llvm/lib/Target/AMDGPU/GCNSchedStrategy.cpp b/llvm/lib/Target/AMDGPU/GCNSchedStrategy.cpp
index 6e693066de10..fce8f36d4596 100644
--- a/llvm/lib/Target/AMDGPU/GCNSchedStrategy.cpp
+++ b/llvm/lib/Target/AMDGPU/GCNSchedStrategy.cpp
@@ -25,8 +25,13 @@
#include "GCNSchedStrategy.h"
#include "AMDGPUIGroupLP.h"
+#include "GCNRegPressure.h"
#include "SIMachineFunctionInfo.h"
+#include "Utils/AMDGPUBaseInfo.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/CodeGen/RegisterClassInfo.h"
+#include "llvm/MC/LaneBitmask.h"
+#include "llvm/Support/ErrorHandling.h"
#define DEBUG_TYPE "machine-scheduler"
@@ -94,17 +99,20 @@ void GCNSchedStrategy::initialize(ScheduleDAGMI *DAG) {
std::min(ST.getMaxNumSGPRs(TargetOccupancy, true), SGPRExcessLimit);
if (!KnownExcessRP) {
- VGPRCriticalLimit =
- std::min(ST.getMaxNumVGPRs(TargetOccupancy), VGPRExcessLimit);
+ VGPRCriticalLimit = std::min(
+ ST.getMaxNumVGPRs(TargetOccupancy, MFI.getDynamicVGPRBlockSize()),
+ VGPRExcessLimit);
} else {
// This is similar to ST.getMaxNumVGPRs(TargetOccupancy) result except
// returns a reasonably small number for targets with lots of VGPRs, such
// as GFX10 and GFX11.
LLVM_DEBUG(dbgs() << "Region is known to spill, use alternative "
"VGPRCriticalLimit calculation method.\n");
-
- unsigned Granule = AMDGPU::IsaInfo::getVGPRAllocGranule(&ST);
- unsigned Addressable = AMDGPU::IsaInfo::getAddressableNumVGPRs(&ST);
+ unsigned DynamicVGPRBlockSize = MFI.getDynamicVGPRBlockSize();
+ unsigned Granule =
+ AMDGPU::IsaInfo::getVGPRAllocGranule(&ST, DynamicVGPRBlockSize);
+ unsigned Addressable =
+ AMDGPU::IsaInfo::getAddressableNumVGPRs(&ST, DynamicVGPRBlockSize);
unsigned VGPRBudget = alignDown(Addressable / TargetOccupancy, Granule);
VGPRBudget = std::max(VGPRBudget, Granule);
VGPRCriticalLimit = std::min(VGPRBudget, VGPRExcessLimit);
@@ -188,12 +196,6 @@ static void getRegisterPressures(
Pressure[AMDGPU::RegisterPressureSets::AGPR_32] = NewPressure.getAGPRNum();
}
-// Return true if the instruction is mutually exclusive with all non-IGLP DAG
-// mutations, requiring all other mutations to be disabled.
-static bool isIGLPMutationOnly(unsigned Opcode) {
- return Opcode == AMDGPU::SCHED_GROUP_BARRIER || Opcode == AMDGPU::IGLP_OPT;
-}
-
void GCNSchedStrategy::initCandidate(SchedCandidate &Cand, SUnit *SU,
bool AtTop,
const RegPressureTracker &RPTracker,
@@ -307,11 +309,11 @@ void GCNSchedStrategy::initCandidate(SchedCandidate &Cand, SUnit *SU,
HasHighPressure = true;
if (SGPRDelta > VGPRDelta) {
Cand.RPDelta.CriticalMax =
- PressureChange(AMDGPU::RegisterPressureSets::SReg_32);
+ PressureChange(AMDGPU::RegisterPressureSets::SReg_32);
Cand.RPDelta.CriticalMax.setUnitInc(SGPRDelta);
} else {
Cand.RPDelta.CriticalMax =
- PressureChange(AMDGPU::RegisterPressureSets::VGPR_32);
+ PressureChange(AMDGPU::RegisterPressureSets::VGPR_32);
Cand.RPDelta.CriticalMax.setUnitInc(VGPRDelta);
}
}
@@ -324,7 +326,7 @@ void GCNSchedStrategy::pickNodeFromQueue(SchedBoundary &Zone,
const RegPressureTracker &RPTracker,
SchedCandidate &Cand,
bool IsBottomUp) {
- const SIRegisterInfo *SRI = static_cast<const SIRegisterInfo*>(TRI);
+ const SIRegisterInfo *SRI = static_cast<const SIRegisterInfo *>(TRI);
ArrayRef<unsigned> Pressure = RPTracker.getRegSetPressureAtPos();
unsigned SGPRPressure = 0;
unsigned VGPRPressure = 0;
@@ -420,7 +422,7 @@ SUnit *GCNSchedStrategy::pickNodeBidirectional(bool &IsTopNode) {
pickNodeFromQueue(Top, TopPolicy, DAG->getTopRPTracker(), TCand,
/*IsBottomUp=*/false);
assert(TCand.SU == TopCand.SU &&
- "Last pick result should correspond to re-picking right now");
+ "Last pick result should correspond to re-picking right now");
}
#endif
}
@@ -590,12 +592,11 @@ bool GCNMaxILPSchedStrategy::tryCandidate(SchedCandidate &Cand,
// This is a best effort to set things up for a post-RA pass. Optimizations
// like generating loads of multiple registers should ideally be done within
// the scheduler pass by combining the loads during DAG postprocessing.
- const SUnit *CandNextClusterSU =
- Cand.AtTop ? DAG->getNextClusterSucc() : DAG->getNextClusterPred();
- const SUnit *TryCandNextClusterSU =
- TryCand.AtTop ? DAG->getNextClusterSucc() : DAG->getNextClusterPred();
- if (tryGreater(TryCand.SU == TryCandNextClusterSU,
- Cand.SU == CandNextClusterSU, TryCand, Cand, Cluster))
+ const ClusterInfo *CandCluster = Cand.AtTop ? TopCluster : BotCluster;
+ const ClusterInfo *TryCandCluster = TryCand.AtTop ? TopCluster : BotCluster;
+ if (tryGreater(TryCandCluster && TryCandCluster->contains(TryCand.SU),
+ CandCluster && CandCluster->contains(Cand.SU), TryCand, Cand,
+ Cluster))
return TryCand.Reason != NoCand;
// Avoid increasing the max critical pressure in the scheduled region.
@@ -665,12 +666,11 @@ bool GCNMaxMemoryClauseSchedStrategy::tryCandidate(SchedCandidate &Cand,
// MaxMemoryClause-specific: We prioritize clustered instructions as we would
// get more benefit from clausing these memory instructions.
- const SUnit *CandNextClusterSU =
- Cand.AtTop ? DAG->getNextClusterSucc() : DAG->getNextClusterPred();
- const SUnit *TryCandNextClusterSU =
- TryCand.AtTop ? DAG->getNextClusterSucc() : DAG->getNextClusterPred();
- if (tryGreater(TryCand.SU == TryCandNextClusterSU,
- Cand.SU == CandNextClusterSU, TryCand, Cand, Cluster))
+ const ClusterInfo *CandCluster = Cand.AtTop ? TopCluster : BotCluster;
+ const ClusterInfo *TryCandCluster = TryCand.AtTop ? TopCluster : BotCluster;
+ if (tryGreater(TryCandCluster && TryCandCluster->contains(TryCand.SU),
+ CandCluster && CandCluster->contains(Cand.SU), TryCand, Cand,
+ Cluster))
return TryCand.Reason != NoCand;
// We only compare a subset of features when comparing nodes between
@@ -760,6 +760,10 @@ GCNScheduleDAGMILive::GCNScheduleDAGMILive(
StartingOccupancy(MFI.getOccupancy()), MinOccupancy(StartingOccupancy),
RegionLiveOuts(this, /*IsLiveOut=*/true) {
+ // We want regions with a single MI to be scheduled so that we can reason
+ // about them correctly during scheduling stages that move MIs between regions
+ // (e.g., rematerialization).
+ ScheduleSingleMIRegions = true;
LLVM_DEBUG(dbgs() << "Starting occupancy is " << StartingOccupancy << ".\n");
if (RelaxedOcc) {
MinOccupancy = std::min(MFI.getMinAllowedOccupancy(), StartingOccupancy);
@@ -869,6 +873,8 @@ void GCNScheduleDAGMILive::computeBlockPressure(unsigned RegionIdx,
Pressure[CurRegion] = RPTracker.moveMaxPressure();
if (CurRegion-- == RegionIdx)
break;
+ auto &Rgn = Regions[CurRegion];
+ NonDbgMI = &*skipDebugInstructionsForward(Rgn.first, Rgn.second);
}
RPTracker.advanceToNext();
RPTracker.advanceBeforeNext();
@@ -890,13 +896,13 @@ GCNScheduleDAGMILive::getRegionLiveInMap() const {
std::vector<MachineInstr *> RegionFirstMIs;
RegionFirstMIs.reserve(Regions.size());
auto I = Regions.rbegin(), E = Regions.rend();
- auto *BB = I->first->getParent();
do {
+ const MachineBasicBlock *MBB = I->first->getParent();
auto *MI = &*skipDebugInstructionsForward(I->first, I->second);
RegionFirstMIs.push_back(MI);
do {
++I;
- } while (I != E && I->first->getParent() == BB);
+ } while (I != E && I->first->getParent() == MBB);
} while (I != E);
return getLiveRegMap(RegionFirstMIs, /*After=*/false, *LIS);
}
@@ -932,12 +938,10 @@ void GCNScheduleDAGMILive::finalizeSchedule() {
// GCNScheduleDAGMILive::schedule().
LiveIns.resize(Regions.size());
Pressure.resize(Regions.size());
- RescheduleRegions.resize(Regions.size());
RegionsWithHighRP.resize(Regions.size());
RegionsWithExcessRP.resize(Regions.size());
RegionsWithMinOcc.resize(Regions.size());
RegionsWithIGLPInstrs.resize(Regions.size());
- RescheduleRegions.set();
RegionsWithHighRP.reset();
RegionsWithExcessRP.reset();
RegionsWithMinOcc.reset();
@@ -1081,31 +1085,46 @@ bool ClusteredLowOccStage::initGCNSchedStage() {
return true;
}
-bool PreRARematStage::initGCNSchedStage() {
- if (!GCNSchedStage::initGCNSchedStage())
- return false;
+/// Allows to easily filter for this stage's debug output.
+#define REMAT_DEBUG(X) LLVM_DEBUG(dbgs() << "[PreRARemat] "; X;)
- if (DAG.RegionsWithMinOcc.none() || DAG.Regions.size() == 1)
- return false;
+bool PreRARematStage::initGCNSchedStage() {
+ // FIXME: This pass will invalidate cached BBLiveInMap and MBBLiveIns for
+ // regions inbetween the defs and region we sinked the def to. Will need to be
+ // fixed if there is another pass after this pass.
+ assert(!S.hasNextStage());
- const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
- // Rematerialization will not help if occupancy is not limited by reg usage.
- if (ST.getOccupancyWithWorkGroupSizes(MF).second == DAG.MinOccupancy)
+ if (!GCNSchedStage::initGCNSchedStage() || DAG.RegionsWithMinOcc.none() ||
+ DAG.Regions.size() == 1)
return false;
- // FIXME: This pass will invalidate cached MBBLiveIns for regions
- // inbetween the defs and region we sinked the def to. Cached pressure
- // for regions where a def is sinked from will also be invalidated. Will
- // need to be fixed if there is another pass after this pass.
- assert(!S.hasNextStage());
+ // Before performing any IR modification record the parent region of each MI
+ // and the parent MBB of each region.
+ const unsigned NumRegions = DAG.Regions.size();
+ RegionBB.reserve(NumRegions);
+ for (unsigned I = 0; I < NumRegions; ++I) {
+ RegionBoundaries Region = DAG.Regions[I];
+ for (auto MI = Region.first; MI != Region.second; ++MI)
+ MIRegion.insert({&*MI, I});
+ RegionBB.push_back(Region.first->getParent());
+ }
- collectRematerializableInstructions();
- if (RematerializableInsts.empty() || !sinkTriviallyRematInsts(ST, TII))
+ if (!canIncreaseOccupancyOrReduceSpill())
return false;
- LLVM_DEBUG(
- dbgs() << "Retrying function scheduling with improved occupancy of "
- << DAG.MinOccupancy << " from rematerializing\n");
+ // Rematerialize identified instructions and update scheduler's state.
+ rematerialize();
+ if (GCNTrackers)
+ DAG.RegionLiveOuts.buildLiveRegMap();
+ REMAT_DEBUG(
+ dbgs() << "Retrying function scheduling with new min. occupancy of "
+ << AchievedOcc << " from rematerializing (original was "
+ << DAG.MinOccupancy << ", target was " << TargetOcc << ")\n");
+ if (AchievedOcc > DAG.MinOccupancy) {
+ DAG.MinOccupancy = AchievedOcc;
+ SIMachineFunctionInfo &MFI = *MF.getInfo<SIMachineFunctionInfo>();
+ MFI.increaseOccupancy(MF, DAG.MinOccupancy);
+ }
return true;
}
@@ -1120,7 +1139,8 @@ void UnclusteredHighRPStage::finalizeGCNSchedStage() {
if (DAG.MinOccupancy > InitialOccupancy) {
for (unsigned IDX = 0; IDX < DAG.Pressure.size(); ++IDX)
DAG.RegionsWithMinOcc[IDX] =
- DAG.Pressure[IDX].getOccupancy(DAG.ST) == DAG.MinOccupancy;
+ DAG.Pressure[IDX].getOccupancy(
+ DAG.ST, DAG.MFI.getDynamicVGPRBlockSize()) == DAG.MinOccupancy;
LLVM_DEBUG(dbgs() << StageID
<< " stage successfully increased occupancy to "
@@ -1155,9 +1175,10 @@ bool GCNSchedStage::initGCNRegion() {
Unsched.reserve(DAG.NumRegionInstrs);
if (StageID == GCNSchedStageID::OccInitialSchedule ||
StageID == GCNSchedStageID::ILPInitialSchedule) {
+ const SIInstrInfo *SII = static_cast<const SIInstrInfo *>(DAG.TII);
for (auto &I : DAG) {
Unsched.push_back(&I);
- if (isIGLPMutationOnly(I.getOpcode()))
+ if (SII->isIGLPMutationOnly(I.getOpcode()))
DAG.RegionsWithIGLPInstrs[RegionIdx] = true;
}
} else {
@@ -1215,10 +1236,7 @@ bool ClusteredLowOccStage::initGCNRegion() {
}
bool PreRARematStage::initGCNRegion() {
- if (!DAG.RescheduleRegions[RegionIdx])
- return false;
-
- return GCNSchedStage::initGCNRegion();
+ return RescheduleRegions[RegionIdx] && GCNSchedStage::initGCNRegion();
}
void GCNSchedStage::setupNewBlock() {
@@ -1237,7 +1255,6 @@ void GCNSchedStage::setupNewBlock() {
void GCNSchedStage::finalizeGCNRegion() {
DAG.Regions[RegionIdx] = std::pair(DAG.RegionBegin, DAG.RegionEnd);
- DAG.RescheduleRegions[RegionIdx] = false;
if (S.HasHighPressure)
DAG.RegionsWithHighRP[RegionIdx] = true;
@@ -1250,7 +1267,7 @@ void GCNSchedStage::finalizeGCNRegion() {
SavedMutations.swap(DAG.Mutations);
DAG.exitRegion();
- RegionIdx++;
+ advanceRegion();
}
void GCNSchedStage::checkScheduling() {
@@ -1260,11 +1277,14 @@ void GCNSchedStage::checkScheduling() {
LLVM_DEBUG(dbgs() << "Pressure after scheduling: " << print(PressureAfter));
LLVM_DEBUG(dbgs() << "Region: " << RegionIdx << ".\n");
+ unsigned DynamicVGPRBlockSize = DAG.MFI.getDynamicVGPRBlockSize();
+
if (PressureAfter.getSGPRNum() <= S.SGPRCriticalLimit &&
PressureAfter.getVGPRNum(ST.hasGFX90AInsts()) <= S.VGPRCriticalLimit) {
DAG.Pressure[RegionIdx] = PressureAfter;
DAG.RegionsWithMinOcc[RegionIdx] =
- PressureAfter.getOccupancy(ST) == DAG.MinOccupancy;
+ PressureAfter.getOccupancy(ST, DynamicVGPRBlockSize) ==
+ DAG.MinOccupancy;
// Early out if we have achieved the occupancy target.
LLVM_DEBUG(dbgs() << "Pressure in desired limits, done.\n");
@@ -1273,10 +1293,10 @@ void GCNSchedStage::checkScheduling() {
unsigned TargetOccupancy = std::min(
S.getTargetOccupancy(), ST.getOccupancyWithWorkGroupSizes(MF).second);
- unsigned WavesAfter =
- std::min(TargetOccupancy, PressureAfter.getOccupancy(ST));
- unsigned WavesBefore =
- std::min(TargetOccupancy, PressureBefore.getOccupancy(ST));
+ unsigned WavesAfter = std::min(
+ TargetOccupancy, PressureAfter.getOccupancy(ST, DynamicVGPRBlockSize));
+ unsigned WavesBefore = std::min(
+ TargetOccupancy, PressureBefore.getOccupancy(ST, DynamicVGPRBlockSize));
LLVM_DEBUG(dbgs() << "Occupancy before scheduling: " << WavesBefore
<< ", after " << WavesAfter << ".\n");
@@ -1311,10 +1331,9 @@ void GCNSchedStage::checkScheduling() {
unsigned MaxSGPRs = ST.getMaxNumSGPRs(MF);
if (PressureAfter.getVGPRNum(ST.hasGFX90AInsts()) > MaxVGPRs ||
- PressureAfter.getVGPRNum(false) > MaxArchVGPRs ||
+ PressureAfter.getArchVGPRNum() > MaxArchVGPRs ||
PressureAfter.getAGPRNum() > MaxArchVGPRs ||
PressureAfter.getSGPRNum() > MaxSGPRs) {
- DAG.RescheduleRegions[RegionIdx] = true;
DAG.RegionsWithHighRP[RegionIdx] = true;
DAG.RegionsWithExcessRP[RegionIdx] = true;
}
@@ -1326,7 +1345,8 @@ void GCNSchedStage::checkScheduling() {
} else {
DAG.Pressure[RegionIdx] = PressureAfter;
DAG.RegionsWithMinOcc[RegionIdx] =
- PressureAfter.getOccupancy(ST) == DAG.MinOccupancy;
+ PressureAfter.getOccupancy(ST, DynamicVGPRBlockSize) ==
+ DAG.MinOccupancy;
}
}
@@ -1448,6 +1468,18 @@ bool GCNSchedStage::shouldRevertScheduling(unsigned WavesAfter) {
if (WavesAfter < DAG.MinOccupancy)
return true;
+ // For dynamic VGPR mode, we don't want to waste any VGPR blocks.
+ if (DAG.MFI.isDynamicVGPREnabled()) {
+ unsigned BlocksBefore = AMDGPU::IsaInfo::getAllocatedNumVGPRBlocks(
+ &ST, DAG.MFI.getDynamicVGPRBlockSize(),
+ PressureBefore.getVGPRNum(false));
+ unsigned BlocksAfter = AMDGPU::IsaInfo::getAllocatedNumVGPRBlocks(
+ &ST, DAG.MFI.getDynamicVGPRBlockSize(),
+ PressureAfter.getVGPRNum(false));
+ if (BlocksAfter > BlocksBefore)
+ return true;
+ }
+
return false;
}
@@ -1467,7 +1499,8 @@ bool OccInitialScheduleStage::shouldRevertScheduling(unsigned WavesAfter) {
bool UnclusteredHighRPStage::shouldRevertScheduling(unsigned WavesAfter) {
// If RP is not reduced in the unclustered reschedule stage, revert to the
// old schedule.
- if ((WavesAfter <= PressureBefore.getOccupancy(ST) &&
+ if ((WavesAfter <=
+ PressureBefore.getOccupancy(ST, DAG.MFI.getDynamicVGPRBlockSize()) &&
mayCauseSpilling(WavesAfter)) ||
GCNSchedStage::shouldRevertScheduling(WavesAfter)) {
LLVM_DEBUG(dbgs() << "Unclustered reschedule did not help.\n");
@@ -1482,16 +1515,16 @@ bool UnclusteredHighRPStage::shouldRevertScheduling(unsigned WavesAfter) {
dbgs()
<< "\n\t *** In shouldRevertScheduling ***\n"
<< " *********** BEFORE UnclusteredHighRPStage ***********\n");
- ScheduleMetrics MBefore =
- getScheduleMetrics(DAG.SUnits);
+ ScheduleMetrics MBefore = getScheduleMetrics(DAG.SUnits);
LLVM_DEBUG(
dbgs()
<< "\n *********** AFTER UnclusteredHighRPStage ***********\n");
ScheduleMetrics MAfter = getScheduleMetrics(DAG);
unsigned OldMetric = MBefore.getMetric();
unsigned NewMetric = MAfter.getMetric();
- unsigned WavesBefore =
- std::min(S.getTargetOccupancy(), PressureBefore.getOccupancy(ST));
+ unsigned WavesBefore = std::min(
+ S.getTargetOccupancy(),
+ PressureBefore.getOccupancy(ST, DAG.MFI.getDynamicVGPRBlockSize()));
unsigned Profit =
((WavesAfter * ScheduleMetrics::ScaleFactor) / WavesBefore *
((OldMetric + ScheduleMetricBias) * ScheduleMetrics::ScaleFactor) /
@@ -1516,13 +1549,9 @@ bool ClusteredLowOccStage::shouldRevertScheduling(unsigned WavesAfter) {
}
bool PreRARematStage::shouldRevertScheduling(unsigned WavesAfter) {
- if (GCNSchedStage::shouldRevertScheduling(WavesAfter))
- return true;
-
- if (mayCauseSpilling(WavesAfter))
- return true;
-
- return false;
+ return GCNSchedStage::shouldRevertScheduling(WavesAfter) ||
+ mayCauseSpilling(WavesAfter) ||
+ (IncreaseOccupancy && WavesAfter < TargetOcc);
}
bool ILPInitialScheduleStage::shouldRevertScheduling(unsigned WavesAfter) {
@@ -1549,11 +1578,9 @@ bool GCNSchedStage::mayCauseSpilling(unsigned WavesAfter) {
void GCNSchedStage::revertScheduling() {
DAG.RegionsWithMinOcc[RegionIdx] =
- PressureBefore.getOccupancy(ST) == DAG.MinOccupancy;
+ PressureBefore.getOccupancy(ST, DAG.MFI.getDynamicVGPRBlockSize()) ==
+ DAG.MinOccupancy;
LLVM_DEBUG(dbgs() << "Attempting to revert scheduling.\n");
- DAG.RescheduleRegions[RegionIdx] =
- S.hasNextStage() &&
- S.getNextStage() != GCNSchedStageID::UnclusteredHighRPReschedule;
DAG.RegionEnd = DAG.RegionBegin;
int SkippedDebugInstr = 0;
for (MachineInstr *MI : Unsched) {
@@ -1563,8 +1590,7 @@ void GCNSchedStage::revertScheduling() {
}
if (MI->getIterator() != DAG.RegionEnd) {
- DAG.BB->remove(MI);
- DAG.BB->insert(DAG.RegionEnd, MI);
+ DAG.BB->splice(DAG.RegionEnd, DAG.BB, MI);
if (!MI->isDebugInstr())
DAG.LIS->handleMove(*MI, true);
}
@@ -1615,241 +1641,396 @@ void GCNSchedStage::revertScheduling() {
DAG.Regions[RegionIdx] = std::pair(DAG.RegionBegin, DAG.RegionEnd);
}
-void PreRARematStage::collectRematerializableInstructions() {
- const SIRegisterInfo *SRI = static_cast<const SIRegisterInfo *>(DAG.TRI);
- for (unsigned I = 0, E = DAG.MRI.getNumVirtRegs(); I != E; ++I) {
- Register Reg = Register::index2VirtReg(I);
- if (!DAG.LIS->hasInterval(Reg))
- continue;
+bool PreRARematStage::allUsesAvailableAt(const MachineInstr *InstToRemat,
+ SlotIndex OriginalIdx,
+ SlotIndex RematIdx) const {
- // TODO: Handle AGPR and SGPR rematerialization
- if (!SRI->isVGPRClass(DAG.MRI.getRegClass(Reg)) ||
- !DAG.MRI.hasOneDef(Reg) || !DAG.MRI.hasOneNonDBGUse(Reg))
+ LiveIntervals *LIS = DAG.LIS;
+ MachineRegisterInfo &MRI = DAG.MRI;
+ OriginalIdx = OriginalIdx.getRegSlot(true);
+ RematIdx = std::max(RematIdx, RematIdx.getRegSlot(true));
+ for (const MachineOperand &MO : InstToRemat->operands()) {
+ if (!MO.isReg() || !MO.getReg() || !MO.readsReg())
continue;
- MachineOperand *Op = DAG.MRI.getOneDef(Reg);
- MachineInstr *Def = Op->getParent();
- if (Op->getSubReg() != 0 || !isTriviallyReMaterializable(*Def))
- continue;
+ if (!MO.getReg().isVirtual()) {
+ // Do not attempt to reason about PhysRegs
+ // TODO: better analysis of PhysReg livness
+ if (!DAG.MRI.isConstantPhysReg(MO.getReg()) &&
+ !DAG.TII->isIgnorableUse(MO))
+ return false;
- MachineInstr *UseI = &*DAG.MRI.use_instr_nodbg_begin(Reg);
- if (Def->getParent() == UseI->getParent())
+ // Constant PhysRegs and IgnorableUses are okay
continue;
+ }
- // We are only collecting defs that are defined in another block and are
- // live-through or used inside regions at MinOccupancy. This means that the
- // register must be in the live-in set for the region.
- bool AddedToRematList = false;
- for (unsigned I = 0, E = DAG.Regions.size(); I != E; ++I) {
- auto It = DAG.LiveIns[I].find(Reg);
- if (It != DAG.LiveIns[I].end() && !It->second.none()) {
- if (DAG.RegionsWithMinOcc[I]) {
- RematerializableInsts[I][Def] = UseI;
- AddedToRematList = true;
- }
+ LiveInterval &LI = LIS->getInterval(MO.getReg());
+ const VNInfo *OVNI = LI.getVNInfoAt(OriginalIdx);
+ assert(OVNI);
+
+ // Don't allow rematerialization immediately after the original def.
+ // It would be incorrect if InstToRemat redefines the register.
+ // See PR14098.
+ if (SlotIndex::isSameInstr(OriginalIdx, RematIdx))
+ return false;
+
+ if (OVNI != LI.getVNInfoAt(RematIdx))
+ return false;
+
+ // Check that subrange is live at RematIdx.
+ if (LI.hasSubRanges()) {
+ const TargetRegisterInfo *TRI = MRI.getTargetRegisterInfo();
+ unsigned SubReg = MO.getSubReg();
+ LaneBitmask LM = SubReg ? TRI->getSubRegIndexLaneMask(SubReg)
+ : MRI.getMaxLaneMaskForVReg(MO.getReg());
+ for (LiveInterval::SubRange &SR : LI.subranges()) {
+ if ((SR.LaneMask & LM).none())
+ continue;
+ if (!SR.liveAt(RematIdx))
+ return false;
- // Collect regions with rematerializable reg as live-in to avoid
- // searching later when updating RP.
- RematDefToLiveInRegions[Def].push_back(I);
+ // Early exit if all used lanes are checked. No need to continue.
+ LM &= ~SR.LaneMask;
+ if (LM.none())
+ break;
}
}
- if (!AddedToRematList)
- RematDefToLiveInRegions.erase(Def);
}
+ return true;
}
-bool PreRARematStage::sinkTriviallyRematInsts(const GCNSubtarget &ST,
- const TargetInstrInfo *TII) {
- // Temporary copies of cached variables we will be modifying and replacing if
- // sinking succeeds.
- SmallVector<
- std::pair<MachineBasicBlock::iterator, MachineBasicBlock::iterator>, 32>
- NewRegions;
- DenseMap<unsigned, GCNRPTracker::LiveRegSet> NewLiveIns;
- DenseMap<unsigned, GCNRegPressure> NewPressure;
- BitVector NewRescheduleRegions;
- LiveIntervals *LIS = DAG.LIS;
+bool PreRARematStage::canIncreaseOccupancyOrReduceSpill() {
+ REMAT_DEBUG({
+ dbgs() << "Collecting rematerializable instructions in ";
+ MF.getFunction().printAsOperand(dbgs(), false);
+ dbgs() << '\n';
+ });
- NewRegions.resize(DAG.Regions.size());
- NewRescheduleRegions.resize(DAG.Regions.size());
+ // Maps optimizable regions (i.e., regions at minimum and register-limited
+ // occupancy, or regions with spilling) to the target RP we would like to
+ // reach.
+ DenseMap<unsigned, GCNRPTarget> OptRegions;
+ const Function &F = MF.getFunction();
+ unsigned DynamicVGPRBlockSize =
+ MF.getInfo<SIMachineFunctionInfo>()->getDynamicVGPRBlockSize();
- // Collect only regions that has a rematerializable def as a live-in.
- SmallSet<unsigned, 16> ImpactedRegions;
- for (const auto &It : RematDefToLiveInRegions)
- ImpactedRegions.insert(It.second.begin(), It.second.end());
+ std::pair<unsigned, unsigned> WavesPerEU = ST.getWavesPerEU(F);
+ const unsigned MaxSGPRsNoSpill = ST.getMaxNumSGPRs(F);
+ const unsigned MaxVGPRsNoSpill = ST.getMaxNumVGPRs(F);
+ const unsigned MaxSGPRsIncOcc =
+ ST.getMaxNumSGPRs(DAG.MinOccupancy + 1, false);
+ const unsigned MaxVGPRsIncOcc =
+ ST.getMaxNumVGPRs(DAG.MinOccupancy + 1, DynamicVGPRBlockSize);
+ IncreaseOccupancy = WavesPerEU.second > DAG.MinOccupancy;
- // Make copies of register pressure and live-ins cache that will be updated
- // as we rematerialize.
- for (auto Idx : ImpactedRegions) {
- NewPressure[Idx] = DAG.Pressure[Idx];
- NewLiveIns[Idx] = DAG.LiveIns[Idx];
+ // Collect optimizable regions. If there is spilling in any region we will
+ // just try to reduce spilling. Otherwise we will try to increase occupancy by
+ // one in the whole function.
+ for (unsigned I = 0, E = DAG.Regions.size(); I != E; ++I) {
+ GCNRegPressure &RP = DAG.Pressure[I];
+ // We allow ArchVGPR or AGPR savings to count as savings of the other kind
+ // of VGPR only when trying to eliminate spilling. We cannot do this when
+ // trying to increase occupancy since VGPR class swaps only occur later in
+ // the register allocator i.e., the scheduler will not be able to reason
+ // about these savings and will not report an increase in the achievable
+ // occupancy, triggering rollbacks.
+ GCNRPTarget Target(MaxSGPRsNoSpill, MaxVGPRsNoSpill, MF, RP,
+ /*CombineVGPRSavings=*/true);
+ if (!Target.satisfied() && IncreaseOccupancy) {
+ // There is spilling in the region and we were so far trying to increase
+ // occupancy. Strop trying that and focus on reducing spilling.
+ IncreaseOccupancy = false;
+ OptRegions.clear();
+ } else if (IncreaseOccupancy) {
+ // There is no spilling in the region, try to increase occupancy.
+ Target = GCNRPTarget(MaxSGPRsIncOcc, MaxVGPRsIncOcc, MF, RP,
+ /*CombineVGPRSavings=*/false);
+ }
+ if (!Target.satisfied())
+ OptRegions.insert({I, Target});
}
- NewRegions = DAG.Regions;
- NewRescheduleRegions.reset();
+ if (OptRegions.empty())
+ return false;
- DenseMap<MachineInstr *, MachineInstr *> InsertedMIToOldDef;
- bool Improved = false;
- for (auto I : ImpactedRegions) {
- if (!DAG.RegionsWithMinOcc[I])
- continue;
+#ifndef NDEBUG
+ if (IncreaseOccupancy) {
+ REMAT_DEBUG(dbgs() << "Occupancy minimal (" << DAG.MinOccupancy
+ << ") in regions:\n");
+ } else {
+ REMAT_DEBUG(dbgs() << "Spilling w.r.t. minimum target occupancy ("
+ << WavesPerEU.first << ") in regions:\n");
+ }
+ for (unsigned I = 0, E = DAG.Regions.size(); I != E; ++I) {
+ if (auto OptIt = OptRegions.find(I); OptIt != OptRegions.end())
+ REMAT_DEBUG(dbgs() << " [" << I << "] " << OptIt->getSecond() << '\n');
+ }
+#endif
- Improved = false;
- int VGPRUsage = NewPressure[I].getVGPRNum(ST.hasGFX90AInsts());
- int SGPRUsage = NewPressure[I].getSGPRNum();
+ // When we are reducing spilling, the target is the minimum target number of
+ // waves/EU determined by the subtarget. In cases where either one of
+ // "amdgpu-num-sgpr" or "amdgpu-num-vgpr" are set on the function, the current
+ // minimum region occupancy may be higher than the latter.
+ TargetOcc = IncreaseOccupancy ? DAG.MinOccupancy + 1
+ : std::max(DAG.MinOccupancy, WavesPerEU.first);
- // TODO: Handle occupancy drop due to AGPR and SGPR.
- // Check if cause of occupancy drop is due to VGPR usage and not SGPR.
- if (ST.getOccupancyWithNumSGPRs(SGPRUsage) == DAG.MinOccupancy)
- break;
+ // Accounts for a reduction in RP in an optimizable region. Returns whether we
+ // estimate that we have identified enough rematerialization opportunities to
+ // achieve our goal, and sets Progress to true when this particular reduction
+ // in pressure was helpful toward that goal.
+ auto ReduceRPInRegion = [&](auto OptIt, Register Reg, LaneBitmask Mask,
+ bool &Progress) -> bool {
+ GCNRPTarget &Target = OptIt->getSecond();
+ if (!Target.isSaveBeneficial(Reg, DAG.MRI))
+ return false;
+ Progress = true;
+ Target.saveReg(Reg, Mask, DAG.MRI);
+ if (Target.satisfied())
+ OptRegions.erase(OptIt->getFirst());
+ return OptRegions.empty();
+ };
- // The occupancy of this region could have been improved by a previous
- // iteration's sinking of defs.
- if (NewPressure[I].getOccupancy(ST) > DAG.MinOccupancy) {
- NewRescheduleRegions[I] = true;
- Improved = true;
- continue;
- }
+ // We need up-to-date live-out info. to query live-out register masks in
+ // regions containing rematerializable instructions.
+ DAG.RegionLiveOuts.buildLiveRegMap();
- // First check if we have enough trivially rematerializable instructions to
- // improve occupancy. Optimistically assume all instructions we are able to
- // sink decreased RP.
- int TotalSinkableRegs = 0;
- for (const auto &It : RematerializableInsts[I]) {
- MachineInstr *Def = It.first;
- Register DefReg = Def->getOperand(0).getReg();
- TotalSinkableRegs +=
- SIRegisterInfo::getNumCoveredRegs(NewLiveIns[I][DefReg]);
- }
- int VGPRsAfterSink = VGPRUsage - TotalSinkableRegs;
- unsigned OptimisticOccupancy = ST.getOccupancyWithNumVGPRs(VGPRsAfterSink);
- // If in the most optimistic scenario, we cannot improve occupancy, then do
- // not attempt to sink any instructions.
- if (OptimisticOccupancy <= DAG.MinOccupancy)
- break;
+ // Cache set of registers that are going to be rematerialized.
+ DenseSet<unsigned> RematRegs;
+
+ // Identify rematerializable instructions in the function.
+ for (unsigned I = 0, E = DAG.Regions.size(); I != E; ++I) {
+ auto Region = DAG.Regions[I];
+ for (auto MI = Region.first; MI != Region.second; ++MI) {
+ // The instruction must be trivially rematerializable.
+ MachineInstr &DefMI = *MI;
+ if (!isTriviallyReMaterializable(DefMI))
+ continue;
+
+ // We only support rematerializing virtual registers with one definition.
+ Register Reg = DefMI.getOperand(0).getReg();
+ if (!Reg.isVirtual() || !DAG.MRI.hasOneDef(Reg))
+ continue;
+
+ // We only care to rematerialize the instruction if it has a single
+ // non-debug user in a different region. The using MI may not belong to a
+ // region if it is a lone region terminator.
+ MachineInstr *UseMI = DAG.MRI.getOneNonDBGUser(Reg);
+ if (!UseMI)
+ continue;
+ auto UseRegion = MIRegion.find(UseMI);
+ if (UseRegion != MIRegion.end() && UseRegion->second == I)
+ continue;
+
+ // Do not rematerialize an instruction if it uses or is used by an
+ // instruction that we have designated for rematerialization.
+ // FIXME: Allow for rematerialization chains: this requires 1. updating
+ // remat points to account for uses that are rematerialized, and 2. either
+ // rematerializing the candidates in careful ordering, or deferring the
+ // MBB RP walk until the entire chain has been rematerialized.
+ if (Rematerializations.contains(UseMI) ||
+ llvm::any_of(DefMI.operands(), [&RematRegs](MachineOperand &MO) {
+ return MO.isReg() && RematRegs.contains(MO.getReg());
+ }))
+ continue;
+
+ // Do not rematerialize an instruction it it uses registers that aren't
+ // available at its use. This ensures that we are not extending any live
+ // range while rematerializing.
+ SlotIndex DefIdx = DAG.LIS->getInstructionIndex(DefMI);
+ SlotIndex UseIdx = DAG.LIS->getInstructionIndex(*UseMI).getRegSlot(true);
+ if (!allUsesAvailableAt(&DefMI, DefIdx, UseIdx))
+ continue;
- unsigned ImproveOccupancy = 0;
- SmallVector<MachineInstr *, 4> SinkedDefs;
- for (auto &It : RematerializableInsts[I]) {
- MachineInstr *Def = It.first;
- MachineBasicBlock::iterator InsertPos =
- MachineBasicBlock::iterator(It.second);
- Register Reg = Def->getOperand(0).getReg();
- // Rematerialize MI to its use block. Since we are only rematerializing
- // instructions that do not have any virtual reg uses, we do not need to
- // call LiveRangeEdit::allUsesAvailableAt() and
- // LiveRangeEdit::canRematerializeAt().
- TII->reMaterialize(*InsertPos->getParent(), InsertPos, Reg,
- Def->getOperand(0).getSubReg(), *Def, *DAG.TRI);
- MachineInstr *NewMI = &*std::prev(InsertPos);
- LIS->InsertMachineInstrInMaps(*NewMI);
- LIS->removeInterval(Reg);
- LIS->createAndComputeVirtRegInterval(Reg);
- InsertedMIToOldDef[NewMI] = Def;
+ REMAT_DEBUG(dbgs() << "Region " << I << ": remat instruction " << DefMI);
+ RematInstruction &Remat =
+ Rematerializations.try_emplace(&DefMI, UseMI).first->second;
- // Update region boundaries in scheduling region we sinked from since we
- // may sink an instruction that was at the beginning or end of its region
- DAG.updateRegionBoundaries(NewRegions, Def, /*NewMI =*/nullptr,
- /*Removing =*/true);
+ bool RematUseful = false;
+ if (auto It = OptRegions.find(I); It != OptRegions.end()) {
+ // Optimistically consider that moving the instruction out of its
+ // defining region will reduce RP in the latter; this assumes that
+ // maximum RP in the region is reached somewhere between the defining
+ // instruction and the end of the region.
+ REMAT_DEBUG(dbgs() << " Defining region is optimizable\n");
+ LaneBitmask Mask = DAG.RegionLiveOuts.getLiveRegsForRegionIdx(I)[Reg];
+ if (ReduceRPInRegion(It, Reg, Mask, RematUseful))
+ return true;
+ }
- // Update region boundaries in region we sinked to.
- DAG.updateRegionBoundaries(NewRegions, InsertPos, NewMI);
+ for (unsigned LIRegion = 0; LIRegion != E; ++LIRegion) {
+ // We are only collecting regions in which the register is a live-in
+ // (and may be live-through).
+ auto It = DAG.LiveIns[LIRegion].find(Reg);
+ if (It == DAG.LiveIns[LIRegion].end() || It->second.none())
+ continue;
+ Remat.LiveInRegions.insert(LIRegion);
- LaneBitmask PrevMask = NewLiveIns[I][Reg];
- // FIXME: Also update cached pressure for where the def was sinked from.
- // Update RP for all regions that has this reg as a live-in and remove
- // the reg from all regions as a live-in.
- for (auto Idx : RematDefToLiveInRegions[Def]) {
- NewLiveIns[Idx].erase(Reg);
- if (InsertPos->getParent() != DAG.Regions[Idx].first->getParent()) {
- // Def is live-through and not used in this block.
- NewPressure[Idx].inc(Reg, PrevMask, LaneBitmask::getNone(), DAG.MRI);
- } else {
- // Def is used and rematerialized into this block.
- GCNDownwardRPTracker RPT(*LIS);
- auto *NonDbgMI = &*skipDebugInstructionsForward(
- NewRegions[Idx].first, NewRegions[Idx].second);
- RPT.reset(*NonDbgMI, &NewLiveIns[Idx]);
- RPT.advance(NewRegions[Idx].second);
- NewPressure[Idx] = RPT.moveMaxPressure();
+ // Account for the reduction in RP due to the rematerialization in an
+ // optimizable region in which the defined register is a live-in. This
+ // is exact for live-through region but optimistic in the using region,
+ // where RP is actually reduced only if maximum RP is reached somewhere
+ // between the beginning of the region and the rematerializable
+ // instruction's use.
+ if (auto It = OptRegions.find(LIRegion); It != OptRegions.end()) {
+ REMAT_DEBUG(dbgs() << " Live-in in region " << LIRegion << '\n');
+ if (ReduceRPInRegion(It, Reg, DAG.LiveIns[LIRegion][Reg],
+ RematUseful))
+ return true;
}
}
- SinkedDefs.push_back(Def);
- ImproveOccupancy = NewPressure[I].getOccupancy(ST);
- if (ImproveOccupancy > DAG.MinOccupancy)
- break;
+ // If the instruction is not a live-in or live-out in any optimizable
+ // region then there is no point in rematerializing it.
+ if (!RematUseful) {
+ Rematerializations.pop_back();
+ REMAT_DEBUG(dbgs() << " No impact, not rematerializing instruction\n");
+ } else {
+ RematRegs.insert(Reg);
+ }
}
+ }
+
+ if (IncreaseOccupancy) {
+ // We were trying to increase occupancy but failed, abort the stage.
+ REMAT_DEBUG(dbgs() << "Cannot increase occupancy\n");
+ Rematerializations.clear();
+ return false;
+ }
+ REMAT_DEBUG(dbgs() << "Can reduce but not eliminate spilling\n");
+ return !Rematerializations.empty();
+}
- // Remove defs we just sinked from all regions' list of sinkable defs
- for (auto &Def : SinkedDefs)
- for (auto TrackedIdx : RematDefToLiveInRegions[Def])
- RematerializableInsts[TrackedIdx].erase(Def);
+void PreRARematStage::rematerialize() {
+ const SIInstrInfo *TII = MF.getSubtarget<GCNSubtarget>().getInstrInfo();
- if (ImproveOccupancy <= DAG.MinOccupancy)
- break;
+ // Collect regions whose RP changes in unpredictable way; we will have to
+ // fully recompute their RP after all rematerailizations.
+ DenseSet<unsigned> RecomputeRP;
- NewRescheduleRegions[I] = true;
- Improved = true;
- }
+ // Rematerialize all instructions.
+ for (auto &[DefMI, Remat] : Rematerializations) {
+ MachineBasicBlock::iterator InsertPos(Remat.UseMI);
+ Register Reg = DefMI->getOperand(0).getReg();
+ unsigned SubReg = DefMI->getOperand(0).getSubReg();
+ unsigned DefRegion = MIRegion.at(DefMI);
- if (!Improved) {
- // Occupancy was not improved for all regions that were at MinOccupancy.
- // Undo sinking and remove newly rematerialized instructions.
- for (auto &Entry : InsertedMIToOldDef) {
- MachineInstr *MI = Entry.first;
- MachineInstr *OldMI = Entry.second;
- Register Reg = MI->getOperand(0).getReg();
- LIS->RemoveMachineInstrFromMaps(*MI);
- MI->eraseFromParent();
- OldMI->clearRegisterDeads(Reg);
- LIS->removeInterval(Reg);
- LIS->createAndComputeVirtRegInterval(Reg);
+ // Rematerialize DefMI to its use block.
+ TII->reMaterialize(*InsertPos->getParent(), InsertPos, Reg, SubReg, *DefMI,
+ *DAG.TRI);
+ Remat.RematMI = &*std::prev(InsertPos);
+ Remat.RematMI->getOperand(0).setSubReg(SubReg);
+ DAG.LIS->InsertMachineInstrInMaps(*Remat.RematMI);
+
+ // Update region boundaries in regions we sinked from (remove defining MI)
+ // and to (insert MI rematerialized in use block). Only then we can erase
+ // the original MI.
+ DAG.updateRegionBoundaries(DAG.Regions[DefRegion], DefMI, nullptr);
+ auto UseRegion = MIRegion.find(Remat.UseMI);
+ if (UseRegion != MIRegion.end()) {
+ DAG.updateRegionBoundaries(DAG.Regions[UseRegion->second], InsertPos,
+ Remat.RematMI);
}
- return false;
- }
+ DAG.LIS->RemoveMachineInstrFromMaps(*DefMI);
+ DefMI->eraseFromParent();
- // Occupancy was improved for all regions.
- for (auto &Entry : InsertedMIToOldDef) {
- MachineInstr *MI = Entry.first;
- MachineInstr *OldMI = Entry.second;
+ // Collect all regions impacted by the rematerialization and update their
+ // live-in/RP information.
+ for (unsigned I : Remat.LiveInRegions) {
+ ImpactedRegions.insert({I, DAG.Pressure[I]});
+ GCNRPTracker::LiveRegSet &RegionLiveIns = DAG.LiveIns[I];
- // Remove OldMI from BBLiveInMap since we are sinking it from its MBB.
- DAG.BBLiveInMap.erase(OldMI);
+#ifdef EXPENSIVE_CHECKS
+ // All uses are known to be available / live at the remat point. Thus, the
+ // uses should already be live in to the region.
+ for (MachineOperand &MO : DefMI->operands()) {
+ if (!MO.isReg() || !MO.getReg() || !MO.readsReg())
+ continue;
- // Remove OldMI and update LIS
- Register Reg = MI->getOperand(0).getReg();
- LIS->RemoveMachineInstrFromMaps(*OldMI);
- OldMI->eraseFromParent();
- LIS->removeInterval(Reg);
- LIS->createAndComputeVirtRegInterval(Reg);
- }
+ Register UseReg = MO.getReg();
+ if (!UseReg.isVirtual())
+ continue;
- // Update live-ins, register pressure, and regions caches.
- for (auto Idx : ImpactedRegions) {
- DAG.LiveIns[Idx] = NewLiveIns[Idx];
- DAG.Pressure[Idx] = NewPressure[Idx];
- DAG.MBBLiveIns.erase(DAG.Regions[Idx].first->getParent());
- }
- DAG.Regions = NewRegions;
- DAG.RescheduleRegions = NewRescheduleRegions;
+ LiveInterval &LI = DAG.LIS->getInterval(UseReg);
+ LaneBitmask LM = DAG.MRI.getMaxLaneMaskForVReg(MO.getReg());
+ if (LI.hasSubRanges() && MO.getSubReg())
+ LM = DAG.TRI->getSubRegIndexLaneMask(MO.getSubReg());
- if (GCNTrackers)
- DAG.RegionLiveOuts.buildLiveRegMap();
+ LaneBitmask LiveInMask = RegionLiveIns.at(UseReg);
+ LaneBitmask UncoveredLanes = LM & ~(LiveInMask & LM);
+ // If this register has lanes not covered by the LiveIns, be sure they
+ // do not map to any subrange. ref:
+ // machine-scheduler-sink-trivial-remats.mir::omitted_subrange
+ if (UncoveredLanes.any()) {
+ assert(LI.hasSubRanges());
+ for (LiveInterval::SubRange &SR : LI.subranges())
+ assert((SR.LaneMask & UncoveredLanes).none());
+ }
+ }
+#endif
+
+ // The register is no longer a live-in in all regions but the one that
+ // contains the single use. In live-through regions, maximum register
+ // pressure decreases predictably so we can directly update it. In the
+ // using region, maximum RP may or may not decrease, so we will mark it
+ // for re-computation after all materializations have taken place.
+ LaneBitmask PrevMask = RegionLiveIns[Reg];
+ RegionLiveIns.erase(Reg);
+ RegMasks.insert({{I, Remat.RematMI->getOperand(0).getReg()}, PrevMask});
+ if (Remat.UseMI->getParent() != DAG.Regions[I].first->getParent())
+ DAG.Pressure[I].inc(Reg, PrevMask, LaneBitmask::getNone(), DAG.MRI);
+ else
+ RecomputeRP.insert(I);
+ }
+ // RP in the region from which the instruction was rematerialized may or may
+ // not decrease.
+ ImpactedRegions.insert({DefRegion, DAG.Pressure[DefRegion]});
+ RecomputeRP.insert(DefRegion);
- SIMachineFunctionInfo &MFI = *MF.getInfo<SIMachineFunctionInfo>();
- MFI.increaseOccupancy(MF, ++DAG.MinOccupancy);
+ // Recompute live interval to reflect the register's rematerialization.
+ Register RematReg = Remat.RematMI->getOperand(0).getReg();
+ DAG.LIS->removeInterval(RematReg);
+ DAG.LIS->createAndComputeVirtRegInterval(RematReg);
+ }
- return true;
+ // All regions impacted by at least one rematerialization must be rescheduled.
+ // Maximum pressure must also be recomputed for all regions where it changed
+ // non-predictably and checked against the target occupancy.
+ AchievedOcc = TargetOcc;
+ for (auto &[I, OriginalRP] : ImpactedRegions) {
+ bool IsEmptyRegion = DAG.Regions[I].first == DAG.Regions[I].second;
+ RescheduleRegions[I] = !IsEmptyRegion;
+ if (!RecomputeRP.contains(I))
+ continue;
+
+ GCNRegPressure RP;
+ if (IsEmptyRegion) {
+ RP = getRegPressure(DAG.MRI, DAG.LiveIns[I]);
+ } else {
+ GCNDownwardRPTracker RPT(*DAG.LIS);
+ auto *NonDbgMI = &*skipDebugInstructionsForward(DAG.Regions[I].first,
+ DAG.Regions[I].second);
+ if (NonDbgMI == DAG.Regions[I].second) {
+ // Region is non-empty but contains only debug instructions.
+ RP = getRegPressure(DAG.MRI, DAG.LiveIns[I]);
+ } else {
+ RPT.reset(*NonDbgMI, &DAG.LiveIns[I]);
+ RPT.advance(DAG.Regions[I].second);
+ RP = RPT.moveMaxPressure();
+ }
+ }
+ DAG.Pressure[I] = RP;
+ AchievedOcc = std::min(
+ AchievedOcc, RP.getOccupancy(ST, MF.getInfo<SIMachineFunctionInfo>()
+ ->getDynamicVGPRBlockSize()));
+ }
+ REMAT_DEBUG(dbgs() << "Achieved occupancy " << AchievedOcc << "\n");
}
+// Copied from MachineLICM
bool PreRARematStage::isTriviallyReMaterializable(const MachineInstr &MI) {
if (!DAG.TII->isTriviallyReMaterializable(MI))
return false;
for (const MachineOperand &MO : MI.all_uses()) {
- if (MO.getReg().isVirtual())
- return false;
-
// We can't remat physreg uses, unless it is a constant or an ignorable
// use (e.g. implicit exec use on VALU instructions)
if (MO.getReg().isPhysical()) {
@@ -1862,51 +2043,89 @@ bool PreRARematStage::isTriviallyReMaterializable(const MachineInstr &MI) {
return true;
}
-// When removing, we will have to check both beginning and ending of the region.
-// When inserting, we will only have to check if we are inserting NewMI in front
-// of a scheduling region and do not need to check the ending since we will only
-// ever be inserting before an already existing MI.
-void GCNScheduleDAGMILive::updateRegionBoundaries(
- SmallVectorImpl<std::pair<MachineBasicBlock::iterator,
- MachineBasicBlock::iterator>> &RegionBoundaries,
- MachineBasicBlock::iterator MI, MachineInstr *NewMI, bool Removing) {
- unsigned I = 0, E = RegionBoundaries.size();
- // Search for first region of the block where MI is located
- while (I != E && MI->getParent() != RegionBoundaries[I].first->getParent())
- ++I;
+void PreRARematStage::finalizeGCNSchedStage() {
+ // We consider that reducing spilling is always beneficial so we never
+ // rollback rematerializations in such cases. It's also possible that
+ // rescheduling lowers occupancy over the one achieved just through remats, in
+ // which case we do not want to rollback either (the rescheduling was already
+ // reverted in PreRARematStage::shouldRevertScheduling in such cases).
+ unsigned MaxOcc = std::max(AchievedOcc, DAG.MinOccupancy);
+ if (!IncreaseOccupancy || MaxOcc >= TargetOcc)
+ return;
- for (; I != E; ++I) {
- if (MI->getParent() != RegionBoundaries[I].first->getParent())
- return;
+ REMAT_DEBUG(dbgs() << "Rolling back all rematerializations\n");
+ const SIInstrInfo *TII = MF.getSubtarget<GCNSubtarget>().getInstrInfo();
- if (Removing && MI == RegionBoundaries[I].first &&
- MI == RegionBoundaries[I].second) {
- // MI is in a region with size 1, after removing, the region will be
- // size 0, set RegionBegin and RegionEnd to pass end of block iterator.
- RegionBoundaries[I] =
- std::pair(MI->getParent()->end(), MI->getParent()->end());
- return;
- }
- if (MI == RegionBoundaries[I].first) {
- if (Removing)
- RegionBoundaries[I] =
- std::pair(std::next(MI), RegionBoundaries[I].second);
- else
- // Inserted NewMI in front of region, set new RegionBegin to NewMI
- RegionBoundaries[I] = std::pair(MachineBasicBlock::iterator(NewMI),
- RegionBoundaries[I].second);
- return;
- }
- if (Removing && MI == RegionBoundaries[I].second) {
- RegionBoundaries[I] = std::pair(RegionBoundaries[I].first, std::prev(MI));
- return;
+ // Rollback the rematerializations.
+ for (const auto &[DefMI, Remat] : Rematerializations) {
+ MachineInstr &RematMI = *Remat.RematMI;
+ unsigned DefRegion = MIRegion.at(DefMI);
+ MachineBasicBlock::iterator InsertPos(DAG.Regions[DefRegion].second);
+ MachineBasicBlock *MBB = RegionBB[DefRegion];
+ Register Reg = RematMI.getOperand(0).getReg();
+ unsigned SubReg = RematMI.getOperand(0).getSubReg();
+
+ // Re-rematerialize MI at the end of its original region. Note that it may
+ // not be rematerialized exactly in the same position as originally within
+ // the region, but it should not matter much.
+ TII->reMaterialize(*MBB, InsertPos, Reg, SubReg, RematMI, *DAG.TRI);
+ MachineInstr *NewMI = &*std::prev(InsertPos);
+ NewMI->getOperand(0).setSubReg(SubReg);
+ DAG.LIS->InsertMachineInstrInMaps(*NewMI);
+
+ auto UseRegion = MIRegion.find(Remat.UseMI);
+ if (UseRegion != MIRegion.end()) {
+ DAG.updateRegionBoundaries(DAG.Regions[UseRegion->second], RematMI,
+ nullptr);
}
+ DAG.updateRegionBoundaries(DAG.Regions[DefRegion], InsertPos, NewMI);
+
+ // Erase rematerialized MI.
+ DAG.LIS->RemoveMachineInstrFromMaps(RematMI);
+ RematMI.eraseFromParent();
+
+ // Recompute live interval for the re-rematerialized register
+ DAG.LIS->removeInterval(Reg);
+ DAG.LIS->createAndComputeVirtRegInterval(Reg);
+
+ // Re-add the register as a live-in in all regions it used to be one in.
+ for (unsigned LIRegion : Remat.LiveInRegions)
+ DAG.LiveIns[LIRegion].insert({Reg, RegMasks.at({LIRegion, Reg})});
}
+
+ // Reset RP in all impacted regions.
+ for (auto &[I, OriginalRP] : ImpactedRegions)
+ DAG.Pressure[I] = OriginalRP;
+
+ GCNSchedStage::finalizeGCNSchedStage();
+}
+
+void GCNScheduleDAGMILive::updateRegionBoundaries(
+ RegionBoundaries &RegionBounds, MachineBasicBlock::iterator MI,
+ MachineInstr *NewMI) {
+ assert((!NewMI || NewMI != RegionBounds.second) &&
+ "cannot remove at region end");
+
+ if (RegionBounds.first == RegionBounds.second) {
+ assert(NewMI && "cannot remove from an empty region");
+ RegionBounds.first = NewMI;
+ return;
+ }
+
+ // We only care for modifications at the beginning of a non-empty region since
+ // the upper region boundary is exclusive.
+ if (MI != RegionBounds.first)
+ return;
+ if (!NewMI)
+ RegionBounds.first = std::next(MI); // Removal
+ else
+ RegionBounds.first = NewMI; // Insertion
}
static bool hasIGLPInstrs(ScheduleDAGInstrs *DAG) {
- return any_of(*DAG, [](MachineBasicBlock::iterator MI) {
- return isIGLPMutationOnly(MI->getOpcode());
+ const SIInstrInfo *SII = static_cast<const SIInstrInfo *>(DAG->TII);
+ return any_of(*DAG, [SII](MachineBasicBlock::iterator MI) {
+ return SII->isIGLPMutationOnly(MI->getOpcode());
});
}