forked from OSchip/llvm-project
Temporarily revert 124765 in an attempt to find the cycle breaking bootstrap.
llvm-svn: 124778
This commit is contained in:
parent
c7da993dee
commit
21933539f2
|
@ -560,7 +560,7 @@ namespace llvm {
|
||||||
|
|
||||||
/// getEqClass - Classify creates equivalence classes numbered 0..N. Return
|
/// getEqClass - Classify creates equivalence classes numbered 0..N. Return
|
||||||
/// the equivalence class assigned the VNI.
|
/// the equivalence class assigned the VNI.
|
||||||
unsigned getEqClass(const VNInfo *VNI) const { return eqClass_[VNI->id]; }
|
unsigned getEqClass(const VNInfo *VNI) { return eqClass_[VNI->id]; }
|
||||||
|
|
||||||
/// Distribute - Distribute values in LIV[0] into a separate LiveInterval
|
/// Distribute - Distribute values in LIV[0] into a separate LiveInterval
|
||||||
/// for each connected component. LIV must have a LiveInterval for each
|
/// for each connected component. LIV must have a LiveInterval for each
|
||||||
|
|
|
@ -78,7 +78,6 @@ public:
|
||||||
iterator begin() const { return newRegs_.begin()+firstNew_; }
|
iterator begin() const { return newRegs_.begin()+firstNew_; }
|
||||||
iterator end() const { return newRegs_.end(); }
|
iterator end() const { return newRegs_.end(); }
|
||||||
unsigned size() const { return newRegs_.size()-firstNew_; }
|
unsigned size() const { return newRegs_.size()-firstNew_; }
|
||||||
bool empty() const { return size() == 0; }
|
|
||||||
LiveInterval *get(unsigned idx) const { return newRegs_[idx+firstNew_]; }
|
LiveInterval *get(unsigned idx) const { return newRegs_[idx+firstNew_]; }
|
||||||
|
|
||||||
/// create - Create a new register with the same class and stack slot as
|
/// create - Create a new register with the same class and stack slot as
|
||||||
|
|
|
@ -645,6 +645,9 @@ void RAGreedy::splitAroundRegion(LiveInterval &VirtReg, unsigned PhysReg,
|
||||||
LiveRangeEdit LREdit(VirtReg, NewVRegs, SpillRegs);
|
LiveRangeEdit LREdit(VirtReg, NewVRegs, SpillRegs);
|
||||||
SplitEditor SE(*SA, *LIS, *VRM, *DomTree, LREdit);
|
SplitEditor SE(*SA, *LIS, *VRM, *DomTree, LREdit);
|
||||||
|
|
||||||
|
// Ranges to add to the register interval after all defs are in place.
|
||||||
|
SmallVector<IndexPair, 8> UseRanges;
|
||||||
|
|
||||||
// Create the main cross-block interval.
|
// Create the main cross-block interval.
|
||||||
SE.openIntv();
|
SE.openIntv();
|
||||||
|
|
||||||
|
@ -681,7 +684,7 @@ void RAGreedy::splitAroundRegion(LiveInterval &VirtReg, unsigned PhysReg,
|
||||||
if (!BI.LiveThrough) {
|
if (!BI.LiveThrough) {
|
||||||
DEBUG(dbgs() << ", not live-through.\n");
|
DEBUG(dbgs() << ", not live-through.\n");
|
||||||
SE.enterIntvBefore(BI.Def);
|
SE.enterIntvBefore(BI.Def);
|
||||||
SE.useIntv(BI.Def, Stop);
|
UseRanges.push_back(IndexPair(BI.Def, Stop));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!RegIn) {
|
if (!RegIn) {
|
||||||
|
@ -689,7 +692,7 @@ void RAGreedy::splitAroundRegion(LiveInterval &VirtReg, unsigned PhysReg,
|
||||||
// Reload just before the first use.
|
// Reload just before the first use.
|
||||||
DEBUG(dbgs() << ", not live-in, enter before first use.\n");
|
DEBUG(dbgs() << ", not live-in, enter before first use.\n");
|
||||||
SE.enterIntvBefore(BI.FirstUse);
|
SE.enterIntvBefore(BI.FirstUse);
|
||||||
SE.useIntv(BI.FirstUse, Stop);
|
UseRanges.push_back(IndexPair(BI.FirstUse, Stop));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
DEBUG(dbgs() << ", live-through.\n");
|
DEBUG(dbgs() << ", live-through.\n");
|
||||||
|
@ -714,7 +717,7 @@ void RAGreedy::splitAroundRegion(LiveInterval &VirtReg, unsigned PhysReg,
|
||||||
DEBUG(dbgs() << ", free use at " << Use << ".\n");
|
DEBUG(dbgs() << ", free use at " << Use << ".\n");
|
||||||
assert(Use <= BI.LastUse && "Couldn't find last use");
|
assert(Use <= BI.LastUse && "Couldn't find last use");
|
||||||
SE.enterIntvBefore(Use);
|
SE.enterIntvBefore(Use);
|
||||||
SE.useIntv(Use, Stop);
|
UseRanges.push_back(IndexPair(Use, Stop));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -723,6 +726,12 @@ void RAGreedy::splitAroundRegion(LiveInterval &VirtReg, unsigned PhysReg,
|
||||||
SE.enterIntvAtEnd(*BI.MBB);
|
SE.enterIntvAtEnd(*BI.MBB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the live-out ranges following the defs.
|
||||||
|
// We must wait until all defs have been inserted, otherwise SplitKit gets
|
||||||
|
// confused about the value mapping.
|
||||||
|
for (unsigned i = 0, e = UseRanges.size(); i != e; ++i)
|
||||||
|
SE.useIntv(UseRanges[i].first, UseRanges[i].second);
|
||||||
|
|
||||||
// Now all defs leading to live bundles are handled, do everything else.
|
// Now all defs leading to live bundles are handled, do everything else.
|
||||||
for (unsigned i = 0, e = LiveBlocks.size(); i != e; ++i) {
|
for (unsigned i = 0, e = LiveBlocks.size(); i != e; ++i) {
|
||||||
BlockInfo &BI = LiveBlocks[i];
|
BlockInfo &BI = LiveBlocks[i];
|
||||||
|
|
|
@ -449,6 +449,7 @@ VNInfo *LiveIntervalMap::mapValue(const VNInfo *ParentVNI, SlotIndex Idx,
|
||||||
// VNInfo. Insert phi-def VNInfos along the path back to IdxMBB.
|
// VNInfo. Insert phi-def VNInfos along the path back to IdxMBB.
|
||||||
DEBUG(dbgs() << "\n Reaching defs for BB#" << IdxMBB->getNumber()
|
DEBUG(dbgs() << "\n Reaching defs for BB#" << IdxMBB->getNumber()
|
||||||
<< " at " << Idx << " in " << *LI << '\n');
|
<< " at " << Idx << " in " << *LI << '\n');
|
||||||
|
DEBUG(dumpCache());
|
||||||
|
|
||||||
// Blocks where LI should be live-in.
|
// Blocks where LI should be live-in.
|
||||||
SmallVector<MachineDomTreeNode*, 16> LiveIn;
|
SmallVector<MachineDomTreeNode*, 16> LiveIn;
|
||||||
|
@ -586,6 +587,7 @@ VNInfo *LiveIntervalMap::mapValue(const VNInfo *ParentVNI, SlotIndex Idx,
|
||||||
assert(IdxVNI && "Didn't find value for Idx");
|
assert(IdxVNI && "Didn't find value for Idx");
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
DEBUG(dumpCache());
|
||||||
// Check the LiveOutCache invariants.
|
// Check the LiveOutCache invariants.
|
||||||
for (LiveOutMap::iterator I = LiveOutCache.begin(), E = LiveOutCache.end();
|
for (LiveOutMap::iterator I = LiveOutCache.begin(), E = LiveOutCache.end();
|
||||||
I != E; ++I) {
|
I != E; ++I) {
|
||||||
|
@ -727,86 +729,69 @@ SplitEditor::SplitEditor(SplitAnalysis &sa,
|
||||||
LiveRangeEdit &edit)
|
LiveRangeEdit &edit)
|
||||||
: sa_(sa), LIS(lis), VRM(vrm),
|
: sa_(sa), LIS(lis), VRM(vrm),
|
||||||
MRI(vrm.getMachineFunction().getRegInfo()),
|
MRI(vrm.getMachineFunction().getRegInfo()),
|
||||||
MDT(mdt),
|
|
||||||
TII(*vrm.getMachineFunction().getTarget().getInstrInfo()),
|
TII(*vrm.getMachineFunction().getTarget().getInstrInfo()),
|
||||||
TRI(*vrm.getMachineFunction().getTarget().getRegisterInfo()),
|
TRI(*vrm.getMachineFunction().getTarget().getRegisterInfo()),
|
||||||
Edit(edit),
|
Edit(edit),
|
||||||
OpenIdx(0),
|
DupLI(LIS, mdt, edit.getParent()),
|
||||||
RegAssign(Allocator)
|
OpenLI(LIS, mdt, edit.getParent())
|
||||||
{
|
{
|
||||||
// We don't need an AliasAnalysis since we will only be performing
|
// We don't need an AliasAnalysis since we will only be performing
|
||||||
// cheap-as-a-copy remats anyway.
|
// cheap-as-a-copy remats anyway.
|
||||||
Edit.anyRematerializable(LIS, TII, 0);
|
Edit.anyRematerializable(LIS, TII, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SplitEditor::dump() const {
|
bool SplitEditor::intervalsLiveAt(SlotIndex Idx) const {
|
||||||
if (RegAssign.empty()) {
|
for (LiveRangeEdit::iterator I = Edit.begin(), E = Edit.end(); I != E; ++I)
|
||||||
dbgs() << " empty\n";
|
if (*I != DupLI.getLI() && (*I)->liveAt(Idx))
|
||||||
return;
|
return true;
|
||||||
}
|
return false;
|
||||||
|
|
||||||
for (RegAssignMap::const_iterator I = RegAssign.begin(); I.valid(); ++I)
|
|
||||||
dbgs() << " [" << I.start() << ';' << I.stop() << "):" << I.value();
|
|
||||||
dbgs() << '\n';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VNInfo *SplitEditor::defFromParent(unsigned RegIdx,
|
VNInfo *SplitEditor::defFromParent(LiveIntervalMap &Reg,
|
||||||
VNInfo *ParentVNI,
|
VNInfo *ParentVNI,
|
||||||
SlotIndex UseIdx,
|
SlotIndex UseIdx,
|
||||||
MachineBasicBlock &MBB,
|
MachineBasicBlock &MBB,
|
||||||
MachineBasicBlock::iterator I) {
|
MachineBasicBlock::iterator I) {
|
||||||
|
VNInfo *VNI = 0;
|
||||||
MachineInstr *CopyMI = 0;
|
MachineInstr *CopyMI = 0;
|
||||||
SlotIndex Def;
|
SlotIndex Def;
|
||||||
LiveInterval *LI = Edit.get(RegIdx);
|
|
||||||
|
|
||||||
// Attempt cheap-as-a-copy rematerialization.
|
// Attempt cheap-as-a-copy rematerialization.
|
||||||
LiveRangeEdit::Remat RM(ParentVNI);
|
LiveRangeEdit::Remat RM(ParentVNI);
|
||||||
if (Edit.canRematerializeAt(RM, UseIdx, true, LIS)) {
|
if (Edit.canRematerializeAt(RM, UseIdx, true, LIS)) {
|
||||||
Def = Edit.rematerializeAt(MBB, I, LI->reg, RM, LIS, TII, TRI);
|
Def = Edit.rematerializeAt(MBB, I, Reg.getLI()->reg, RM,
|
||||||
|
LIS, TII, TRI);
|
||||||
} else {
|
} else {
|
||||||
// Can't remat, just insert a copy from parent.
|
// Can't remat, just insert a copy from parent.
|
||||||
CopyMI = BuildMI(MBB, I, DebugLoc(), TII.get(TargetOpcode::COPY), LI->reg)
|
CopyMI = BuildMI(MBB, I, DebugLoc(), TII.get(TargetOpcode::COPY),
|
||||||
.addReg(Edit.getReg());
|
Reg.getLI()->reg).addReg(Edit.getReg());
|
||||||
Def = LIS.InsertMachineInstrInMaps(CopyMI).getDefIndex();
|
Def = LIS.InsertMachineInstrInMaps(CopyMI).getDefIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the value in Reg.
|
// Define the value in Reg.
|
||||||
VNInfo *VNI = LIMappers[RegIdx].defValue(ParentVNI, Def);
|
VNI = Reg.defValue(ParentVNI, Def);
|
||||||
VNI->setCopy(CopyMI);
|
VNI->setCopy(CopyMI);
|
||||||
|
|
||||||
// Add minimal liveness for the new value.
|
// Add minimal liveness for the new value.
|
||||||
Edit.get(RegIdx)->addRange(LiveRange(Def, Def.getNextSlot(), VNI));
|
if (UseIdx < Def)
|
||||||
|
UseIdx = Def;
|
||||||
if (RegIdx) {
|
Reg.getLI()->addRange(LiveRange(Def, UseIdx.getNextSlot(), VNI));
|
||||||
if (UseIdx < Def)
|
|
||||||
UseIdx = Def;
|
|
||||||
RegAssign.insert(Def, UseIdx.getNextSlot(), RegIdx);
|
|
||||||
}
|
|
||||||
return VNI;
|
return VNI;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new virtual register and live interval.
|
/// Create a new virtual register and live interval.
|
||||||
void SplitEditor::openIntv() {
|
void SplitEditor::openIntv() {
|
||||||
assert(!OpenIdx && "Previous LI not closed before openIntv");
|
assert(!OpenLI.getLI() && "Previous LI not closed before openIntv");
|
||||||
|
if (!DupLI.getLI())
|
||||||
|
DupLI.reset(&Edit.create(MRI, LIS, VRM));
|
||||||
|
|
||||||
// Create the complement as index 0.
|
OpenLI.reset(&Edit.create(MRI, LIS, VRM));
|
||||||
if (Edit.empty()) {
|
|
||||||
Edit.create(MRI, LIS, VRM);
|
|
||||||
LIMappers.push_back(LiveIntervalMap(LIS, MDT, Edit.getParent()));
|
|
||||||
LIMappers.back().reset(Edit.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the open interval.
|
|
||||||
OpenIdx = Edit.size();
|
|
||||||
Edit.create(MRI, LIS, VRM);
|
|
||||||
LIMappers.push_back(LiveIntervalMap(LIS, MDT, Edit.getParent()));
|
|
||||||
LIMappers[OpenIdx].reset(Edit.get(OpenIdx));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// enterIntvBefore - Enter OpenLI before the instruction at Idx. If CurLI is
|
/// enterIntvBefore - Enter OpenLI before the instruction at Idx. If CurLI is
|
||||||
/// not live before Idx, a COPY is not inserted.
|
/// not live before Idx, a COPY is not inserted.
|
||||||
void SplitEditor::enterIntvBefore(SlotIndex Idx) {
|
void SplitEditor::enterIntvBefore(SlotIndex Idx) {
|
||||||
assert(OpenIdx && "openIntv not called before enterIntvBefore");
|
assert(OpenLI.getLI() && "openIntv not called before enterIntvBefore");
|
||||||
Idx = Idx.getUseIndex();
|
Idx = Idx.getUseIndex();
|
||||||
DEBUG(dbgs() << " enterIntvBefore " << Idx);
|
DEBUG(dbgs() << " enterIntvBefore " << Idx);
|
||||||
VNInfo *ParentVNI = Edit.getParent().getVNInfoAt(Idx);
|
VNInfo *ParentVNI = Edit.getParent().getVNInfoAt(Idx);
|
||||||
|
@ -815,16 +800,18 @@ void SplitEditor::enterIntvBefore(SlotIndex Idx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DEBUG(dbgs() << ": valno " << ParentVNI->id);
|
DEBUG(dbgs() << ": valno " << ParentVNI->id);
|
||||||
|
truncatedValues.insert(ParentVNI);
|
||||||
MachineInstr *MI = LIS.getInstructionFromIndex(Idx);
|
MachineInstr *MI = LIS.getInstructionFromIndex(Idx);
|
||||||
assert(MI && "enterIntvBefore called with invalid index");
|
assert(MI && "enterIntvBefore called with invalid index");
|
||||||
|
|
||||||
defFromParent(OpenIdx, ParentVNI, Idx, *MI->getParent(), MI);
|
defFromParent(OpenLI, ParentVNI, Idx, *MI->getParent(), MI);
|
||||||
DEBUG(dump());
|
|
||||||
|
DEBUG(dbgs() << ": " << *OpenLI.getLI() << '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// enterIntvAtEnd - Enter OpenLI at the end of MBB.
|
/// enterIntvAtEnd - Enter OpenLI at the end of MBB.
|
||||||
void SplitEditor::enterIntvAtEnd(MachineBasicBlock &MBB) {
|
void SplitEditor::enterIntvAtEnd(MachineBasicBlock &MBB) {
|
||||||
assert(OpenIdx && "openIntv not called before enterIntvAtEnd");
|
assert(OpenLI.getLI() && "openIntv not called before enterIntvAtEnd");
|
||||||
SlotIndex End = LIS.getMBBEndIdx(&MBB).getPrevSlot();
|
SlotIndex End = LIS.getMBBEndIdx(&MBB).getPrevSlot();
|
||||||
DEBUG(dbgs() << " enterIntvAtEnd BB#" << MBB.getNumber() << ", " << End);
|
DEBUG(dbgs() << " enterIntvAtEnd BB#" << MBB.getNumber() << ", " << End);
|
||||||
VNInfo *ParentVNI = Edit.getParent().getVNInfoAt(End);
|
VNInfo *ParentVNI = Edit.getParent().getVNInfoAt(End);
|
||||||
|
@ -833,8 +820,9 @@ void SplitEditor::enterIntvAtEnd(MachineBasicBlock &MBB) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DEBUG(dbgs() << ": valno " << ParentVNI->id);
|
DEBUG(dbgs() << ": valno " << ParentVNI->id);
|
||||||
defFromParent(OpenIdx, ParentVNI, End, MBB, MBB.getFirstTerminator());
|
truncatedValues.insert(ParentVNI);
|
||||||
DEBUG(dump());
|
defFromParent(OpenLI, ParentVNI, End, MBB, MBB.getFirstTerminator());
|
||||||
|
DEBUG(dbgs() << ": " << *OpenLI.getLI() << '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// useIntv - indicate that all instructions in MBB should use OpenLI.
|
/// useIntv - indicate that all instructions in MBB should use OpenLI.
|
||||||
|
@ -843,15 +831,15 @@ void SplitEditor::useIntv(const MachineBasicBlock &MBB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SplitEditor::useIntv(SlotIndex Start, SlotIndex End) {
|
void SplitEditor::useIntv(SlotIndex Start, SlotIndex End) {
|
||||||
assert(OpenIdx && "openIntv not called before useIntv");
|
assert(OpenLI.getLI() && "openIntv not called before useIntv");
|
||||||
DEBUG(dbgs() << " useIntv [" << Start << ';' << End << "):");
|
OpenLI.addRange(Start, End);
|
||||||
RegAssign.insert(Start, End, OpenIdx);
|
DEBUG(dbgs() << " use [" << Start << ';' << End << "): "
|
||||||
DEBUG(dump());
|
<< *OpenLI.getLI() << '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// leaveIntvAfter - Leave OpenLI after the instruction at Idx.
|
/// leaveIntvAfter - Leave OpenLI after the instruction at Idx.
|
||||||
void SplitEditor::leaveIntvAfter(SlotIndex Idx) {
|
void SplitEditor::leaveIntvAfter(SlotIndex Idx) {
|
||||||
assert(OpenIdx && "openIntv not called before leaveIntvAfter");
|
assert(OpenLI.getLI() && "openIntv not called before leaveIntvAfter");
|
||||||
DEBUG(dbgs() << " leaveIntvAfter " << Idx);
|
DEBUG(dbgs() << " leaveIntvAfter " << Idx);
|
||||||
|
|
||||||
// The interval must be live beyond the instruction at Idx.
|
// The interval must be live beyond the instruction at Idx.
|
||||||
|
@ -864,17 +852,20 @@ void SplitEditor::leaveIntvAfter(SlotIndex Idx) {
|
||||||
DEBUG(dbgs() << ": valno " << ParentVNI->id);
|
DEBUG(dbgs() << ": valno " << ParentVNI->id);
|
||||||
|
|
||||||
MachineBasicBlock::iterator MII = LIS.getInstructionFromIndex(Idx);
|
MachineBasicBlock::iterator MII = LIS.getInstructionFromIndex(Idx);
|
||||||
VNInfo *VNI = defFromParent(0, ParentVNI, Idx,
|
VNInfo *VNI = defFromParent(DupLI, ParentVNI, Idx,
|
||||||
*MII->getParent(), llvm::next(MII));
|
*MII->getParent(), llvm::next(MII));
|
||||||
|
|
||||||
RegAssign.insert(Idx, VNI->def, OpenIdx);
|
// Make sure that OpenLI is properly extended from Idx to the new copy.
|
||||||
DEBUG(dump());
|
// FIXME: This shouldn't be necessary for remats.
|
||||||
|
OpenLI.addSimpleRange(Idx, VNI->def, ParentVNI);
|
||||||
|
|
||||||
|
DEBUG(dbgs() << ": " << *OpenLI.getLI() << '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// leaveIntvAtTop - Leave the interval at the top of MBB.
|
/// leaveIntvAtTop - Leave the interval at the top of MBB.
|
||||||
/// Currently, only one value can leave the interval.
|
/// Currently, only one value can leave the interval.
|
||||||
void SplitEditor::leaveIntvAtTop(MachineBasicBlock &MBB) {
|
void SplitEditor::leaveIntvAtTop(MachineBasicBlock &MBB) {
|
||||||
assert(OpenIdx && "openIntv not called before leaveIntvAtTop");
|
assert(OpenLI.getLI() && "openIntv not called before leaveIntvAtTop");
|
||||||
SlotIndex Start = LIS.getMBBStartIdx(&MBB);
|
SlotIndex Start = LIS.getMBBStartIdx(&MBB);
|
||||||
DEBUG(dbgs() << " leaveIntvAtTop BB#" << MBB.getNumber() << ", " << Start);
|
DEBUG(dbgs() << " leaveIntvAtTop BB#" << MBB.getNumber() << ", " << Start);
|
||||||
|
|
||||||
|
@ -884,130 +875,179 @@ void SplitEditor::leaveIntvAtTop(MachineBasicBlock &MBB) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VNInfo *VNI = defFromParent(0, ParentVNI, Start, MBB,
|
VNInfo *VNI = defFromParent(DupLI, ParentVNI, Start, MBB,
|
||||||
MBB.SkipPHIsAndLabels(MBB.begin()));
|
MBB.SkipPHIsAndLabels(MBB.begin()));
|
||||||
RegAssign.insert(Start, VNI->def, OpenIdx);
|
|
||||||
DEBUG(dump());
|
// Finally we must make sure that OpenLI is properly extended from Start to
|
||||||
|
// the new copy.
|
||||||
|
OpenLI.addSimpleRange(Start, VNI->def, ParentVNI);
|
||||||
|
DEBUG(dbgs() << ": " << *OpenLI.getLI() << '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// closeIntv - Indicate that we are done editing the currently open
|
/// closeIntv - Indicate that we are done editing the currently open
|
||||||
/// LiveInterval, and ranges can be trimmed.
|
/// LiveInterval, and ranges can be trimmed.
|
||||||
void SplitEditor::closeIntv() {
|
void SplitEditor::closeIntv() {
|
||||||
assert(OpenIdx && "openIntv not called before closeIntv");
|
assert(OpenLI.getLI() && "openIntv not called before closeIntv");
|
||||||
OpenIdx = 0;
|
DEBUG(dbgs() << " closeIntv " << *OpenLI.getLI() << '\n');
|
||||||
|
OpenLI.reset(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// rewriteAssigned - Rewrite all uses of Edit.getReg().
|
/// rewrite - Rewrite all uses of reg to use the new registers.
|
||||||
void SplitEditor::rewriteAssigned() {
|
void SplitEditor::rewrite(unsigned reg) {
|
||||||
for (MachineRegisterInfo::reg_iterator RI = MRI.reg_begin(Edit.getReg()),
|
for (MachineRegisterInfo::reg_iterator RI = MRI.reg_begin(reg),
|
||||||
RE = MRI.reg_end(); RI != RE;) {
|
RE = MRI.reg_end(); RI != RE;) {
|
||||||
MachineOperand &MO = RI.getOperand();
|
MachineOperand &MO = RI.getOperand();
|
||||||
|
unsigned OpNum = RI.getOperandNo();
|
||||||
MachineInstr *MI = MO.getParent();
|
MachineInstr *MI = MO.getParent();
|
||||||
++RI;
|
++RI;
|
||||||
// LiveDebugVariables should have handled all DBG_VALUE instructions.
|
|
||||||
if (MI->isDebugValue()) {
|
if (MI->isDebugValue()) {
|
||||||
DEBUG(dbgs() << "Zapping " << *MI);
|
DEBUG(dbgs() << "Zapping " << *MI);
|
||||||
|
// FIXME: We can do much better with debug values.
|
||||||
MO.setReg(0);
|
MO.setReg(0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
SlotIndex Idx = LIS.getInstructionIndex(MI);
|
SlotIndex Idx = LIS.getInstructionIndex(MI);
|
||||||
Idx = MO.isUse() ? Idx.getUseIndex() : Idx.getDefIndex();
|
Idx = MO.isUse() ? Idx.getUseIndex() : Idx.getDefIndex();
|
||||||
|
LiveInterval *LI = 0;
|
||||||
// Rewrite to the mapped register at Idx.
|
for (LiveRangeEdit::iterator I = Edit.begin(), E = Edit.end(); I != E;
|
||||||
unsigned RegIdx = RegAssign.lookup(Idx);
|
++I) {
|
||||||
MO.setReg(Edit.get(RegIdx)->reg);
|
LiveInterval *testli = *I;
|
||||||
DEBUG(dbgs() << " rewr BB#" << MI->getParent()->getNumber() << '\t'
|
if (testli->liveAt(Idx)) {
|
||||||
<< Idx << ':' << RegIdx << '\t' << *MI);
|
LI = testli;
|
||||||
|
break;
|
||||||
// Extend liveness to Idx.
|
}
|
||||||
const VNInfo *ParentVNI = Edit.getParent().getVNInfoAt(Idx);
|
}
|
||||||
LIMappers[RegIdx].mapValue(ParentVNI, Idx);
|
DEBUG(dbgs() << " rewr BB#" << MI->getParent()->getNumber() << '\t'<< Idx);
|
||||||
|
assert(LI && "No register was live at use");
|
||||||
|
MO.setReg(LI->reg);
|
||||||
|
if (MO.isUse() && !MI->isRegTiedToDefOperand(OpNum))
|
||||||
|
MO.setIsKill(LI->killedAt(Idx.getDefIndex()));
|
||||||
|
DEBUG(dbgs() << '\t' << *MI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// rewriteSplit - Rewrite uses of Intvs[0] according to the ConEQ mapping.
|
void
|
||||||
void SplitEditor::rewriteComponents(const SmallVectorImpl<LiveInterval*> &Intvs,
|
SplitEditor::addTruncSimpleRange(SlotIndex Start, SlotIndex End, VNInfo *VNI) {
|
||||||
const ConnectedVNInfoEqClasses &ConEq) {
|
// Build vector of iterator pairs from the intervals.
|
||||||
for (MachineRegisterInfo::reg_iterator RI = MRI.reg_begin(Intvs[0]->reg),
|
typedef std::pair<LiveInterval::const_iterator,
|
||||||
RE = MRI.reg_end(); RI != RE;) {
|
LiveInterval::const_iterator> IIPair;
|
||||||
MachineOperand &MO = RI.getOperand();
|
SmallVector<IIPair, 8> Iters;
|
||||||
MachineInstr *MI = MO.getParent();
|
for (LiveRangeEdit::iterator LI = Edit.begin(), LE = Edit.end(); LI != LE;
|
||||||
++RI;
|
++LI) {
|
||||||
if (MO.isUse() && MO.isUndef())
|
if (*LI == DupLI.getLI())
|
||||||
continue;
|
continue;
|
||||||
// DBG_VALUE instructions should have been eliminated earlier.
|
LiveInterval::const_iterator I = (*LI)->find(Start);
|
||||||
SlotIndex Idx = LIS.getInstructionIndex(MI);
|
LiveInterval::const_iterator E = (*LI)->end();
|
||||||
Idx = MO.isUse() ? Idx.getUseIndex() : Idx.getDefIndex();
|
if (I != E)
|
||||||
DEBUG(dbgs() << " rewr BB#" << MI->getParent()->getNumber() << '\t'
|
Iters.push_back(std::make_pair(I, E));
|
||||||
<< Idx << ':');
|
}
|
||||||
const VNInfo *VNI = Intvs[0]->getVNInfoAt(Idx);
|
|
||||||
assert(VNI && "Interval not live at use.");
|
SlotIndex sidx = Start;
|
||||||
MO.setReg(Intvs[ConEq.getEqClass(VNI)]->reg);
|
// Break [Start;End) into segments that don't overlap any intervals.
|
||||||
DEBUG(dbgs() << VNI->id << '\t' << *MI);
|
for (;;) {
|
||||||
|
SlotIndex next = sidx, eidx = End;
|
||||||
|
// Find overlapping intervals.
|
||||||
|
for (unsigned i = 0; i != Iters.size() && sidx < eidx; ++i) {
|
||||||
|
LiveInterval::const_iterator I = Iters[i].first;
|
||||||
|
// Interval I is overlapping [sidx;eidx). Trim sidx.
|
||||||
|
if (I->start <= sidx) {
|
||||||
|
sidx = I->end;
|
||||||
|
// Move to the next run, remove iters when all are consumed.
|
||||||
|
I = ++Iters[i].first;
|
||||||
|
if (I == Iters[i].second) {
|
||||||
|
Iters.erase(Iters.begin() + i);
|
||||||
|
--i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Trim eidx too if needed.
|
||||||
|
if (I->start >= eidx)
|
||||||
|
continue;
|
||||||
|
eidx = I->start;
|
||||||
|
next = I->end;
|
||||||
|
}
|
||||||
|
// Now, [sidx;eidx) doesn't overlap anything in intervals_.
|
||||||
|
if (sidx < eidx)
|
||||||
|
DupLI.addSimpleRange(sidx, eidx, VNI);
|
||||||
|
// If the interval end was truncated, we can try again from next.
|
||||||
|
if (next <= sidx)
|
||||||
|
break;
|
||||||
|
sidx = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SplitEditor::finish() {
|
void SplitEditor::computeRemainder() {
|
||||||
assert(OpenIdx == 0 && "Previous LI not closed before rewrite");
|
// First we need to fill in the live ranges in dupli.
|
||||||
|
// If values were redefined, we need a full recoloring with SSA update.
|
||||||
|
// If values were truncated, we only need to truncate the ranges.
|
||||||
|
// If values were partially rematted, we should shrink to uses.
|
||||||
|
// If values were fully rematted, they should be omitted.
|
||||||
|
// FIXME: If a single value is redefined, just move the def and truncate.
|
||||||
|
LiveInterval &parent = Edit.getParent();
|
||||||
|
|
||||||
// At this point, the live intervals in Edit contain VNInfos corresponding to
|
DEBUG(dbgs() << "computeRemainder from " << parent << '\n');
|
||||||
// the inserted copies.
|
|
||||||
|
|
||||||
// Add the original defs from the parent interval.
|
// Values that are fully contained in the split intervals.
|
||||||
for (LiveInterval::const_vni_iterator I = Edit.getParent().vni_begin(),
|
SmallPtrSet<const VNInfo*, 8> deadValues;
|
||||||
E = Edit.getParent().vni_end(); I != E; ++I) {
|
// Map all CurLI values that should have live defs in dupli.
|
||||||
const VNInfo *ParentVNI = *I;
|
for (LiveInterval::const_vni_iterator I = parent.vni_begin(),
|
||||||
LiveIntervalMap &LIM = LIMappers[RegAssign.lookup(ParentVNI->def)];
|
E = parent.vni_end(); I != E; ++I) {
|
||||||
VNInfo *VNI = LIM.defValue(ParentVNI, ParentVNI->def);
|
const VNInfo *VNI = *I;
|
||||||
LIM.getLI()->addRange(LiveRange(ParentVNI->def,
|
// Don't transfer unused values to the new intervals.
|
||||||
ParentVNI->def.getNextSlot(), VNI));
|
if (VNI->isUnused())
|
||||||
// Mark all values as complex to force liveness computation.
|
continue;
|
||||||
// This should really only be necessary for remat victims, but we are lazy.
|
// Original def is contained in the split intervals.
|
||||||
LIM.markComplexMapped(ParentVNI);
|
if (intervalsLiveAt(VNI->def)) {
|
||||||
|
// Did this value escape?
|
||||||
|
if (DupLI.isMapped(VNI))
|
||||||
|
truncatedValues.insert(VNI);
|
||||||
|
else
|
||||||
|
deadValues.insert(VNI);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Add minimal live range at the definition.
|
||||||
|
VNInfo *DVNI = DupLI.defValue(VNI, VNI->def);
|
||||||
|
DupLI.getLI()->addRange(LiveRange(VNI->def, VNI->def.getNextSlot(), DVNI));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
// Add all ranges to dupli.
|
||||||
// Every new interval must have a def by now, otherwise the split is bogus.
|
for (LiveInterval::const_iterator I = parent.begin(), E = parent.end();
|
||||||
for (LiveRangeEdit::iterator I = Edit.begin(), E = Edit.end(); I != E; ++I)
|
I != E; ++I) {
|
||||||
assert((*I)->hasAtLeastOneValue() && "Split interval has no value");
|
const LiveRange &LR = *I;
|
||||||
#endif
|
if (truncatedValues.count(LR.valno)) {
|
||||||
|
// recolor after removing intervals_.
|
||||||
// FIXME: Don't recompute the liveness of all values, infer it from the
|
addTruncSimpleRange(LR.start, LR.end, LR.valno);
|
||||||
// overlaps between the parent live interval and RegAssign.
|
} else if (!deadValues.count(LR.valno)) {
|
||||||
// The mapValue algorithm is only necessary when:
|
// recolor without truncation.
|
||||||
// - The parent value maps to multiple defs, and new phis are needed, or
|
DupLI.addSimpleRange(LR.start, LR.end, LR.valno);
|
||||||
// - The value has been rematerialized before some uses, and we want to
|
|
||||||
// minimize the live range so it only reaches the remaining uses.
|
|
||||||
// All other values have simple liveness that can be computed from RegAssign
|
|
||||||
// and the parent live interval.
|
|
||||||
|
|
||||||
// Extend live ranges to be live-out for successor PHI values.
|
|
||||||
for (LiveInterval::const_vni_iterator I = Edit.getParent().vni_begin(),
|
|
||||||
E = Edit.getParent().vni_end(); I != E; ++I) {
|
|
||||||
const VNInfo *PHIVNI = *I;
|
|
||||||
if (!PHIVNI->isPHIDef())
|
|
||||||
continue;
|
|
||||||
LiveIntervalMap &LIM = LIMappers[RegAssign.lookup(PHIVNI->def)];
|
|
||||||
MachineBasicBlock *MBB = LIS.getMBBFromIndex(PHIVNI->def);
|
|
||||||
for (MachineBasicBlock::pred_iterator PI = MBB->pred_begin(),
|
|
||||||
PE = MBB->pred_end(); PI != PE; ++PI) {
|
|
||||||
SlotIndex End = LIS.getMBBEndIdx(*PI).getPrevSlot();
|
|
||||||
// The predecessor may not have a live-out value. That is OK, like an
|
|
||||||
// undef PHI operand.
|
|
||||||
if (VNInfo *VNI = Edit.getParent().getVNInfoAt(End))
|
|
||||||
LIM.mapValue(VNI, End);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewrite instructions.
|
// Extend DupLI to be live out of any critical loop predecessors.
|
||||||
rewriteAssigned();
|
// This means we have multiple registers live out of those blocks.
|
||||||
|
// The alternative would be to split the critical edges.
|
||||||
|
if (criticalPreds_.empty())
|
||||||
|
return;
|
||||||
|
for (SplitAnalysis::BlockPtrSet::iterator I = criticalPreds_.begin(),
|
||||||
|
E = criticalPreds_.end(); I != E; ++I)
|
||||||
|
DupLI.extendTo(*I, LIS.getMBBEndIdx(*I).getPrevSlot());
|
||||||
|
criticalPreds_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Delete defs that were rematted everywhere.
|
void SplitEditor::finish() {
|
||||||
|
assert(!OpenLI.getLI() && "Previous LI not closed before rewrite");
|
||||||
|
assert(DupLI.getLI() && "No dupli for rewrite. Noop spilt?");
|
||||||
|
|
||||||
|
// Complete dupli liveness.
|
||||||
|
computeRemainder();
|
||||||
|
|
||||||
// Get rid of unused values and set phi-kill flags.
|
// Get rid of unused values and set phi-kill flags.
|
||||||
for (LiveRangeEdit::iterator I = Edit.begin(), E = Edit.end(); I != E; ++I)
|
for (LiveRangeEdit::iterator I = Edit.begin(), E = Edit.end(); I != E; ++I)
|
||||||
(*I)->RenumberValues(LIS);
|
(*I)->RenumberValues(LIS);
|
||||||
|
|
||||||
|
// Rewrite instructions.
|
||||||
|
rewrite(Edit.getReg());
|
||||||
|
|
||||||
// Now check if any registers were separated into multiple components.
|
// Now check if any registers were separated into multiple components.
|
||||||
ConnectedVNInfoEqClasses ConEQ(LIS);
|
ConnectedVNInfoEqClasses ConEQ(LIS);
|
||||||
for (unsigned i = 0, e = Edit.size(); i != e; ++i) {
|
for (unsigned i = 0, e = Edit.size(); i != e; ++i) {
|
||||||
|
@ -1021,8 +1061,9 @@ void SplitEditor::finish() {
|
||||||
dups.push_back(li);
|
dups.push_back(li);
|
||||||
for (unsigned i = 1; i != NumComp; ++i)
|
for (unsigned i = 1; i != NumComp; ++i)
|
||||||
dups.push_back(&Edit.create(MRI, LIS, VRM));
|
dups.push_back(&Edit.create(MRI, LIS, VRM));
|
||||||
rewriteComponents(dups, ConEQ);
|
|
||||||
ConEQ.Distribute(&dups[0]);
|
ConEQ.Distribute(&dups[0]);
|
||||||
|
// Rewrite uses to the new regs.
|
||||||
|
rewrite(li->reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate spill weight and allocation hints for new intervals.
|
// Calculate spill weight and allocation hints for new intervals.
|
||||||
|
@ -1054,6 +1095,9 @@ void SplitEditor::splitAroundLoop(const MachineLoop *Loop) {
|
||||||
sa_.getCriticalExits(Blocks, CriticalExits);
|
sa_.getCriticalExits(Blocks, CriticalExits);
|
||||||
assert(CriticalExits.empty() && "Cannot break critical exits yet");
|
assert(CriticalExits.empty() && "Cannot break critical exits yet");
|
||||||
|
|
||||||
|
// Get critical predecessors so computeRemainder can deal with them.
|
||||||
|
sa_.getCriticalPreds(Blocks, criticalPreds_);
|
||||||
|
|
||||||
// Create new live interval for the loop.
|
// Create new live interval for the loop.
|
||||||
openIntv();
|
openIntv();
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,11 @@
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "llvm/ADT/IntervalMap.h"
|
|
||||||
#include "llvm/ADT/SmallPtrSet.h"
|
#include "llvm/ADT/SmallPtrSet.h"
|
||||||
#include "llvm/CodeGen/SlotIndexes.h"
|
#include "llvm/CodeGen/SlotIndexes.h"
|
||||||
|
|
||||||
namespace llvm {
|
namespace llvm {
|
||||||
|
|
||||||
class ConnectedVNInfoEqClasses;
|
|
||||||
class LiveInterval;
|
class LiveInterval;
|
||||||
class LiveIntervals;
|
class LiveIntervals;
|
||||||
class LiveRangeEdit;
|
class LiveRangeEdit;
|
||||||
|
@ -265,10 +263,6 @@ public:
|
||||||
/// with defValue.
|
/// with defValue.
|
||||||
bool isComplexMapped(const VNInfo *ParentVNI) const;
|
bool isComplexMapped(const VNInfo *ParentVNI) const;
|
||||||
|
|
||||||
/// markComplexMapped - Mark ParentVNI as complex mapped regardless of the
|
|
||||||
/// number of definitions.
|
|
||||||
void markComplexMapped(const VNInfo *ParentVNI) { Values[ParentVNI] = 0; }
|
|
||||||
|
|
||||||
// addSimpleRange - Add a simple range from ParentLI to LI.
|
// addSimpleRange - Add a simple range from ParentLI to LI.
|
||||||
// ParentVNI must be live in the [Start;End) interval.
|
// ParentVNI must be live in the [Start;End) interval.
|
||||||
void addSimpleRange(SlotIndex Start, SlotIndex End, const VNInfo *ParentVNI);
|
void addSimpleRange(SlotIndex Start, SlotIndex End, const VNInfo *ParentVNI);
|
||||||
|
@ -296,49 +290,49 @@ class SplitEditor {
|
||||||
LiveIntervals &LIS;
|
LiveIntervals &LIS;
|
||||||
VirtRegMap &VRM;
|
VirtRegMap &VRM;
|
||||||
MachineRegisterInfo &MRI;
|
MachineRegisterInfo &MRI;
|
||||||
MachineDominatorTree &MDT;
|
|
||||||
const TargetInstrInfo &TII;
|
const TargetInstrInfo &TII;
|
||||||
const TargetRegisterInfo &TRI;
|
const TargetRegisterInfo &TRI;
|
||||||
|
|
||||||
/// Edit - The current parent register and new intervals created.
|
/// Edit - The current parent register and new intervals created.
|
||||||
LiveRangeEdit &Edit;
|
LiveRangeEdit &Edit;
|
||||||
|
|
||||||
/// Index into Edit of the currently open interval.
|
/// DupLI - Created as a copy of CurLI, ranges are carved out as new
|
||||||
/// The index 0 is used for the complement, so the first interval started by
|
/// intervals get added through openIntv / closeIntv. This is used to avoid
|
||||||
/// openIntv will be 1.
|
/// editing CurLI.
|
||||||
unsigned OpenIdx;
|
LiveIntervalMap DupLI;
|
||||||
|
|
||||||
typedef IntervalMap<SlotIndex, unsigned> RegAssignMap;
|
/// Currently open LiveInterval.
|
||||||
|
LiveIntervalMap OpenLI;
|
||||||
/// Allocator for the interval map. This will eventually be shared with
|
|
||||||
/// SlotIndexes and LiveIntervals.
|
|
||||||
RegAssignMap::Allocator Allocator;
|
|
||||||
|
|
||||||
/// RegAssign - Map of the assigned register indexes.
|
|
||||||
/// Edit.get(RegAssign.lookup(Idx)) is the register that should be live at
|
|
||||||
/// Idx.
|
|
||||||
RegAssignMap RegAssign;
|
|
||||||
|
|
||||||
/// LIMappers - One LiveIntervalMap or each interval in Edit.
|
|
||||||
SmallVector<LiveIntervalMap, 4> LIMappers;
|
|
||||||
|
|
||||||
/// defFromParent - Define Reg from ParentVNI at UseIdx using either
|
/// defFromParent - Define Reg from ParentVNI at UseIdx using either
|
||||||
/// rematerialization or a COPY from parent. Return the new value.
|
/// rematerialization or a COPY from parent. Return the new value.
|
||||||
VNInfo *defFromParent(unsigned RegIdx,
|
VNInfo *defFromParent(LiveIntervalMap &Reg,
|
||||||
VNInfo *ParentVNI,
|
VNInfo *ParentVNI,
|
||||||
SlotIndex UseIdx,
|
SlotIndex UseIdx,
|
||||||
MachineBasicBlock &MBB,
|
MachineBasicBlock &MBB,
|
||||||
MachineBasicBlock::iterator I);
|
MachineBasicBlock::iterator I);
|
||||||
|
|
||||||
/// rewriteAssigned - Rewrite all uses of Edit.getReg() to assigned registers.
|
/// intervalsLiveAt - Return true if any member of intervals_ is live at Idx.
|
||||||
void rewriteAssigned();
|
bool intervalsLiveAt(SlotIndex Idx) const;
|
||||||
|
|
||||||
/// rewriteComponents - Rewrite all uses of Intv[0] according to the eq
|
/// Values in CurLI whose live range has been truncated when entering an open
|
||||||
/// classes in ConEQ.
|
/// li.
|
||||||
/// This must be done when Intvs[0] is styill live at all uses, before calling
|
SmallPtrSet<const VNInfo*, 8> truncatedValues;
|
||||||
/// ConEq.Distribute().
|
|
||||||
void rewriteComponents(const SmallVectorImpl<LiveInterval*> &Intvs,
|
/// addTruncSimpleRange - Add the given simple range to DupLI after
|
||||||
const ConnectedVNInfoEqClasses &ConEq);
|
/// truncating any overlap with intervals_.
|
||||||
|
void addTruncSimpleRange(SlotIndex Start, SlotIndex End, VNInfo *VNI);
|
||||||
|
|
||||||
|
/// criticalPreds_ - Set of basic blocks where both dupli and OpenLI should be
|
||||||
|
/// live out because of a critical edge.
|
||||||
|
SplitAnalysis::BlockPtrSet criticalPreds_;
|
||||||
|
|
||||||
|
/// computeRemainder - Compute the dupli liveness as the complement of all the
|
||||||
|
/// new intervals.
|
||||||
|
void computeRemainder();
|
||||||
|
|
||||||
|
/// rewrite - Rewrite all uses of reg to use the new registers.
|
||||||
|
void rewrite(unsigned reg);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Create a new SplitEditor for editing the LiveInterval analyzed by SA.
|
/// Create a new SplitEditor for editing the LiveInterval analyzed by SA.
|
||||||
|
@ -380,9 +374,6 @@ public:
|
||||||
/// remaining live range, and rewrite instructions to use the new registers.
|
/// remaining live range, and rewrite instructions to use the new registers.
|
||||||
void finish();
|
void finish();
|
||||||
|
|
||||||
/// dump - print the current interval maping to dbgs().
|
|
||||||
void dump() const;
|
|
||||||
|
|
||||||
// ===--- High level methods ---===
|
// ===--- High level methods ---===
|
||||||
|
|
||||||
/// splitAroundLoop - Split CurLI into a separate live interval inside
|
/// splitAroundLoop - Split CurLI into a separate live interval inside
|
||||||
|
|
Loading…
Reference in New Issue