Summary:
This is just an idea, really two ideas. I expect some push-back,
but I realize that posting a diff is the most comprehensive way to express
these concepts.
This patch introduces a Stage class which represents the
various stages of an instruction pipeline. As a start, I have created a simple
FetchStage that is based on existing logic for how MCA produces
instructions, but now encapsulated in a Stage. The idea should become more concrete
once we introduce additional stages. The idea being, that when a stage completes,
the next stage in the pipeline will be executed. Stages are chained together
as a singly linked list to closely model a real pipeline. For now there is only one stage,
so the stage-to-stage flow of instructions isn't immediately obvious.
Eventually, Stage will also handle event notifications, but that functionality
is not complete, and not destined for this patch. Ideally, an interested party
can register for notifications from a particular stage. Callbacks will be issued to
these listeners at various points in the execution of the stage.
For now, eventing functionality remains similar to what it has been in mca::Backend.
We will be building-up the Stage class as we move on, such as adding debug output.
This patch also removes the unique_ptr<Instruction> return value from
InstrBuilder::createInstruction. An Instruction pointer is still produced,
but now it's up to the caller to decide how that item should be managed post-allocation
(e.g., smart pointer). This allows the Fetch stage to create instructions and
manage the lifetime of those instructions as it wishes, and not have to be bound to any
specific managed pointer type. Other callers of createInstruction might have different
requirements, and thus can manage the pointer to fit their needs. Another idea would be to push the
ownership to the RCU.
Currently, the FetchStage will wrap the Instruction
pointer in a shared_ptr. This allows us to remove the Instruction container in
Backend, which was probably going to disappear, or move, at some point anyways.
Note that I did run these changes through valgrind, to make sure we are not leaking
memory. While the shared_ptr comes with some additional overhead it relieves us
from having to manage a list of generated instructions, and/or make lookup calls
to remove the instructions.
I realize that both the Stage class and the Instruction pointer management
(mentioned directly above) are separate but related ideas, and probably should
land as separate patches; I am happy to do that if either idea is decent.
The main reason these two ideas are together is that
Stage::execute() can mutate an InstRef. For the fetch stage, the InstRef is populated
as the primary action of that stage (execute()). I didn't want to change the Stage interface
to support the idea of generating an instruction. Ideally, instructions are to
be pushed through the pipeline. I didn't want to draw too much of a
specialization just for the fetch stage. Excuse the word-salad.
Reviewers: andreadb, courbet, RKSimon
Reviewed By: andreadb
Subscribers: llvm-commits, mgorny, javed.absar, tschuett, gbedwell
Differential Revision: https://reviews.llvm.org/D46741
llvm-svn: 332390
387 lines
14 KiB
C++
387 lines
14 KiB
C++
//===--------------------- Dispatch.cpp -------------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
/// \file
|
|
///
|
|
/// This file implements methods declared by class RegisterFile and
|
|
/// DispatchUnit.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Dispatch.h"
|
|
#include "Backend.h"
|
|
#include "HWEventListener.h"
|
|
#include "Scheduler.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "llvm-mca"
|
|
|
|
namespace mca {
|
|
|
|
void RegisterFile::initialize(const MCSchedModel &SM, unsigned NumRegs) {
|
|
// Create a default register file that "sees" all the machine registers
|
|
// declared by the target. The number of physical registers in the default
|
|
// register file is set equal to `NumRegs`. A value of zero for `NumRegs`
|
|
// means: this register file has an unbounded number of physical registers.
|
|
addRegisterFile({} /* all registers */, NumRegs);
|
|
if (!SM.hasExtraProcessorInfo())
|
|
return;
|
|
|
|
// For each user defined register file, allocate a RegisterMappingTracker
|
|
// object. The size of every register file, as well as the mapping between
|
|
// register files and register classes is specified via tablegen.
|
|
const MCExtraProcessorInfo &Info = SM.getExtraProcessorInfo();
|
|
for (unsigned I = 0, E = Info.NumRegisterFiles; I < E; ++I) {
|
|
const MCRegisterFileDesc &RF = Info.RegisterFiles[I];
|
|
// Skip invalid register files with zero physical registers.
|
|
unsigned Length = RF.NumRegisterCostEntries;
|
|
if (!RF.NumPhysRegs)
|
|
continue;
|
|
// The cost of a register definition is equivalent to the number of
|
|
// physical registers that are allocated at register renaming stage.
|
|
const MCRegisterCostEntry *FirstElt =
|
|
&Info.RegisterCostTable[RF.RegisterCostEntryIdx];
|
|
addRegisterFile(ArrayRef<MCRegisterCostEntry>(FirstElt, Length),
|
|
RF.NumPhysRegs);
|
|
}
|
|
}
|
|
|
|
void RegisterFile::addRegisterFile(ArrayRef<MCRegisterCostEntry> Entries,
|
|
unsigned NumPhysRegs) {
|
|
// A default register file is always allocated at index #0. That register file
|
|
// is mainly used to count the total number of mappings created by all
|
|
// register files at runtime. Users can limit the number of available physical
|
|
// registers in register file #0 through the command line flag
|
|
// `-register-file-size`.
|
|
unsigned RegisterFileIndex = RegisterFiles.size();
|
|
RegisterFiles.emplace_back(NumPhysRegs);
|
|
|
|
// Special case where there is no register class identifier in the set.
|
|
// An empty set of register classes means: this register file contains all
|
|
// the physical registers specified by the target.
|
|
if (Entries.empty()) {
|
|
for (std::pair<WriteState *, IndexPlusCostPairTy> &Mapping :
|
|
RegisterMappings)
|
|
Mapping.second = std::make_pair(RegisterFileIndex, 1U);
|
|
return;
|
|
}
|
|
|
|
// Now update the cost of individual registers.
|
|
for (const MCRegisterCostEntry &RCE : Entries) {
|
|
const MCRegisterClass &RC = MRI.getRegClass(RCE.RegisterClassID);
|
|
for (const MCPhysReg Reg : RC) {
|
|
IndexPlusCostPairTy &Entry = RegisterMappings[Reg].second;
|
|
if (Entry.first) {
|
|
// The only register file that is allowed to overlap is the default
|
|
// register file at index #0. The analysis is inaccurate if register
|
|
// files overlap.
|
|
errs() << "warning: register " << MRI.getName(Reg)
|
|
<< " defined in multiple register files.";
|
|
}
|
|
Entry.first = RegisterFileIndex;
|
|
Entry.second = RCE.Cost;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RegisterFile::allocatePhysRegs(IndexPlusCostPairTy Entry,
|
|
MutableArrayRef<unsigned> UsedPhysRegs) {
|
|
unsigned RegisterFileIndex = Entry.first;
|
|
unsigned Cost = Entry.second;
|
|
if (RegisterFileIndex) {
|
|
RegisterMappingTracker &RMT = RegisterFiles[RegisterFileIndex];
|
|
RMT.NumUsedMappings += Cost;
|
|
UsedPhysRegs[RegisterFileIndex] += Cost;
|
|
}
|
|
|
|
// Now update the default register mapping tracker.
|
|
RegisterFiles[0].NumUsedMappings += Cost;
|
|
UsedPhysRegs[0] += Cost;
|
|
}
|
|
|
|
void RegisterFile::freePhysRegs(IndexPlusCostPairTy Entry,
|
|
MutableArrayRef<unsigned> FreedPhysRegs) {
|
|
unsigned RegisterFileIndex = Entry.first;
|
|
unsigned Cost = Entry.second;
|
|
if (RegisterFileIndex) {
|
|
RegisterMappingTracker &RMT = RegisterFiles[RegisterFileIndex];
|
|
RMT.NumUsedMappings -= Cost;
|
|
FreedPhysRegs[RegisterFileIndex] += Cost;
|
|
}
|
|
|
|
// Now update the default register mapping tracker.
|
|
RegisterFiles[0].NumUsedMappings -= Cost;
|
|
FreedPhysRegs[0] += Cost;
|
|
}
|
|
|
|
void RegisterFile::addRegisterWrite(WriteState &WS,
|
|
MutableArrayRef<unsigned> UsedPhysRegs,
|
|
bool ShouldAllocatePhysRegs) {
|
|
unsigned RegID = WS.getRegisterID();
|
|
assert(RegID && "Adding an invalid register definition?");
|
|
|
|
RegisterMapping &Mapping = RegisterMappings[RegID];
|
|
Mapping.first = &WS;
|
|
for (MCSubRegIterator I(RegID, &MRI); I.isValid(); ++I)
|
|
RegisterMappings[*I].first = &WS;
|
|
|
|
// No physical registers are allocated for instructions that are optimized in
|
|
// hardware. For example, zero-latency data-dependency breaking instructions
|
|
// don't consume physical registers.
|
|
if (ShouldAllocatePhysRegs)
|
|
allocatePhysRegs(Mapping.second, UsedPhysRegs);
|
|
|
|
// If this is a partial update, then we are done.
|
|
if (!WS.fullyUpdatesSuperRegs())
|
|
return;
|
|
|
|
for (MCSuperRegIterator I(RegID, &MRI); I.isValid(); ++I)
|
|
RegisterMappings[*I].first = &WS;
|
|
}
|
|
|
|
void RegisterFile::removeRegisterWrite(const WriteState &WS,
|
|
MutableArrayRef<unsigned> FreedPhysRegs,
|
|
bool ShouldFreePhysRegs) {
|
|
unsigned RegID = WS.getRegisterID();
|
|
bool ShouldInvalidateSuperRegs = WS.fullyUpdatesSuperRegs();
|
|
|
|
assert(RegID != 0 && "Invalidating an already invalid register?");
|
|
assert(WS.getCyclesLeft() != -512 &&
|
|
"Invalidating a write of unknown cycles!");
|
|
assert(WS.getCyclesLeft() <= 0 && "Invalid cycles left for this write!");
|
|
RegisterMapping &Mapping = RegisterMappings[RegID];
|
|
if (!Mapping.first)
|
|
return;
|
|
|
|
if (ShouldFreePhysRegs)
|
|
freePhysRegs(Mapping.second, FreedPhysRegs);
|
|
|
|
if (Mapping.first == &WS)
|
|
Mapping.first = nullptr;
|
|
|
|
for (MCSubRegIterator I(RegID, &MRI); I.isValid(); ++I)
|
|
if (RegisterMappings[*I].first == &WS)
|
|
RegisterMappings[*I].first = nullptr;
|
|
|
|
if (!ShouldInvalidateSuperRegs)
|
|
return;
|
|
|
|
for (MCSuperRegIterator I(RegID, &MRI); I.isValid(); ++I)
|
|
if (RegisterMappings[*I].first == &WS)
|
|
RegisterMappings[*I].first = nullptr;
|
|
}
|
|
|
|
void RegisterFile::collectWrites(SmallVectorImpl<WriteState *> &Writes,
|
|
unsigned RegID) const {
|
|
assert(RegID && RegID < RegisterMappings.size());
|
|
WriteState *WS = RegisterMappings[RegID].first;
|
|
if (WS) {
|
|
LLVM_DEBUG(dbgs() << "Found a dependent use of RegID=" << RegID << '\n');
|
|
Writes.push_back(WS);
|
|
}
|
|
|
|
// Handle potential partial register updates.
|
|
for (MCSubRegIterator I(RegID, &MRI); I.isValid(); ++I) {
|
|
WS = RegisterMappings[*I].first;
|
|
if (WS && std::find(Writes.begin(), Writes.end(), WS) == Writes.end()) {
|
|
LLVM_DEBUG(dbgs() << "Found a dependent use of subReg " << *I
|
|
<< " (part of " << RegID << ")\n");
|
|
Writes.push_back(WS);
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned RegisterFile::isAvailable(ArrayRef<unsigned> Regs) const {
|
|
SmallVector<unsigned, 4> NumPhysRegs(getNumRegisterFiles());
|
|
|
|
// Find how many new mappings must be created for each register file.
|
|
for (const unsigned RegID : Regs) {
|
|
const IndexPlusCostPairTy &Entry = RegisterMappings[RegID].second;
|
|
if (Entry.first)
|
|
NumPhysRegs[Entry.first] += Entry.second;
|
|
NumPhysRegs[0] += Entry.second;
|
|
}
|
|
|
|
unsigned Response = 0;
|
|
for (unsigned I = 0, E = getNumRegisterFiles(); I < E; ++I) {
|
|
unsigned NumRegs = NumPhysRegs[I];
|
|
if (!NumRegs)
|
|
continue;
|
|
|
|
const RegisterMappingTracker &RMT = RegisterFiles[I];
|
|
if (!RMT.TotalMappings) {
|
|
// The register file has an unbounded number of microarchitectural
|
|
// registers.
|
|
continue;
|
|
}
|
|
|
|
if (RMT.TotalMappings < NumRegs) {
|
|
// The current register file is too small. This may occur if the number of
|
|
// microarchitectural registers in register file #0 was changed by the
|
|
// users via flag -reg-file-size. Alternatively, the scheduling model
|
|
// specified a too small number of registers for this register file.
|
|
report_fatal_error(
|
|
"Not enough microarchitectural registers in the register file");
|
|
}
|
|
|
|
if (RMT.TotalMappings < (RMT.NumUsedMappings + NumRegs))
|
|
Response |= (1U << I);
|
|
}
|
|
|
|
return Response;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
void RegisterFile::dump() const {
|
|
for (unsigned I = 0, E = MRI.getNumRegs(); I < E; ++I) {
|
|
const RegisterMapping &RM = RegisterMappings[I];
|
|
dbgs() << MRI.getName(I) << ", " << I << ", Map=" << RM.second.first
|
|
<< ", ";
|
|
if (RM.first)
|
|
RM.first->dump();
|
|
else
|
|
dbgs() << "(null)\n";
|
|
}
|
|
|
|
for (unsigned I = 0, E = getNumRegisterFiles(); I < E; ++I) {
|
|
dbgs() << "Register File #" << I;
|
|
const RegisterMappingTracker &RMT = RegisterFiles[I];
|
|
dbgs() << "\n TotalMappings: " << RMT.TotalMappings
|
|
<< "\n NumUsedMappings: " << RMT.NumUsedMappings << '\n';
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void DispatchUnit::notifyInstructionDispatched(const InstRef &IR,
|
|
ArrayRef<unsigned> UsedRegs) {
|
|
LLVM_DEBUG(dbgs() << "[E] Instruction Dispatched: " << IR << '\n');
|
|
Owner->notifyInstructionEvent(HWInstructionDispatchedEvent(IR, UsedRegs));
|
|
}
|
|
|
|
void DispatchUnit::notifyInstructionRetired(const InstRef &IR) {
|
|
LLVM_DEBUG(dbgs() << "[E] Instruction Retired: " << IR << '\n');
|
|
SmallVector<unsigned, 4> FreedRegs(RAT->getNumRegisterFiles());
|
|
const InstrDesc &Desc = IR.getInstruction()->getDesc();
|
|
|
|
for (const std::unique_ptr<WriteState> &WS : IR.getInstruction()->getDefs())
|
|
RAT->removeRegisterWrite(*WS.get(), FreedRegs, !Desc.isZeroLatency());
|
|
Owner->notifyInstructionEvent(HWInstructionRetiredEvent(IR, FreedRegs));
|
|
}
|
|
|
|
bool DispatchUnit::checkRAT(const InstRef &IR) {
|
|
SmallVector<unsigned, 4> RegDefs;
|
|
for (const std::unique_ptr<WriteState> &RegDef :
|
|
IR.getInstruction()->getDefs())
|
|
RegDefs.emplace_back(RegDef->getRegisterID());
|
|
|
|
unsigned RegisterMask = RAT->isAvailable(RegDefs);
|
|
// A mask with all zeroes means: register files are available.
|
|
if (RegisterMask) {
|
|
Owner->notifyStallEvent(HWStallEvent(HWStallEvent::RegisterFileStall, IR));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DispatchUnit::checkRCU(const InstRef &IR) {
|
|
const unsigned NumMicroOps = IR.getInstruction()->getDesc().NumMicroOps;
|
|
if (RCU->isAvailable(NumMicroOps))
|
|
return true;
|
|
Owner->notifyStallEvent(
|
|
HWStallEvent(HWStallEvent::RetireControlUnitStall, IR));
|
|
return false;
|
|
}
|
|
|
|
bool DispatchUnit::checkScheduler(const InstRef &IR) {
|
|
return SC->canBeDispatched(IR);
|
|
}
|
|
|
|
void DispatchUnit::updateRAWDependencies(ReadState &RS,
|
|
const MCSubtargetInfo &STI) {
|
|
SmallVector<WriteState *, 4> DependentWrites;
|
|
|
|
collectWrites(DependentWrites, RS.getRegisterID());
|
|
RS.setDependentWrites(DependentWrites.size());
|
|
LLVM_DEBUG(dbgs() << "Found " << DependentWrites.size()
|
|
<< " dependent writes\n");
|
|
// We know that this read depends on all the writes in DependentWrites.
|
|
// For each write, check if we have ReadAdvance information, and use it
|
|
// to figure out in how many cycles this read becomes available.
|
|
const ReadDescriptor &RD = RS.getDescriptor();
|
|
if (!RD.HasReadAdvanceEntries) {
|
|
for (WriteState *WS : DependentWrites)
|
|
WS->addUser(&RS, /* ReadAdvance */ 0);
|
|
return;
|
|
}
|
|
|
|
const MCSchedModel &SM = STI.getSchedModel();
|
|
const MCSchedClassDesc *SC = SM.getSchedClassDesc(RD.SchedClassID);
|
|
for (WriteState *WS : DependentWrites) {
|
|
unsigned WriteResID = WS->getWriteResourceID();
|
|
int ReadAdvance = STI.getReadAdvanceCycles(SC, RD.UseIndex, WriteResID);
|
|
WS->addUser(&RS, ReadAdvance);
|
|
}
|
|
// Prepare the set for another round.
|
|
DependentWrites.clear();
|
|
}
|
|
|
|
void DispatchUnit::dispatch(InstRef IR, const MCSubtargetInfo &STI) {
|
|
assert(!CarryOver && "Cannot dispatch another instruction!");
|
|
Instruction &IS = *IR.getInstruction();
|
|
const InstrDesc &Desc = IS.getDesc();
|
|
const unsigned NumMicroOps = Desc.NumMicroOps;
|
|
if (NumMicroOps > DispatchWidth) {
|
|
assert(AvailableEntries == DispatchWidth);
|
|
AvailableEntries = 0;
|
|
CarryOver = NumMicroOps - DispatchWidth;
|
|
} else {
|
|
assert(AvailableEntries >= NumMicroOps);
|
|
AvailableEntries -= NumMicroOps;
|
|
}
|
|
|
|
// A dependency-breaking instruction doesn't have to wait on the register
|
|
// input operands, and it is often optimized at register renaming stage.
|
|
// Update RAW dependencies if this instruction is not a dependency-breaking
|
|
// instruction. A dependency-breaking instruction is a zero-latency
|
|
// instruction that doesn't consume hardware resources.
|
|
// An example of dependency-breaking instruction on X86 is a zero-idiom XOR.
|
|
if (!Desc.isZeroLatency())
|
|
for (std::unique_ptr<ReadState> &RS : IS.getUses())
|
|
updateRAWDependencies(*RS, STI);
|
|
|
|
// By default, a dependency-breaking zero-latency instruction is expected to
|
|
// be optimized at register renaming stage. That means, no physical register
|
|
// is allocated to the instruction.
|
|
SmallVector<unsigned, 4> RegisterFiles(RAT->getNumRegisterFiles());
|
|
for (std::unique_ptr<WriteState> &WS : IS.getDefs())
|
|
RAT->addRegisterWrite(*WS, RegisterFiles, !Desc.isZeroLatency());
|
|
|
|
// Reserve slots in the RCU, and notify the instruction that it has been
|
|
// dispatched to the schedulers for execution.
|
|
IS.dispatch(RCU->reserveSlot(IR, NumMicroOps));
|
|
|
|
// Notify listeners of the "instruction dispatched" event.
|
|
notifyInstructionDispatched(IR, RegisterFiles);
|
|
|
|
// Now move the instruction into the scheduler's queue.
|
|
// The scheduler is responsible for checking if this is a zero-latency
|
|
// instruction that doesn't consume pipeline/scheduler resources.
|
|
SC->scheduleInstruction(IR);
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
void DispatchUnit::dump() const {
|
|
RAT->dump();
|
|
RCU->dump();
|
|
}
|
|
#endif
|
|
} // namespace mca
|