diff --git a/llvm/include/llvm/MC/MCSchedule.h b/llvm/include/llvm/MC/MCSchedule.h --- a/llvm/include/llvm/MC/MCSchedule.h +++ b/llvm/include/llvm/MC/MCSchedule.h @@ -63,9 +63,14 @@ struct MCWriteProcResEntry { uint16_t ProcResourceIdx; uint16_t Cycles; + // Cycle at which the resource will be grabbed by an instruction, + // relatively to the cycle in which the instruction is issued + // (assuming no stalls inbetween). + uint16_t StartAtCycle; bool operator==(const MCWriteProcResEntry &Other) const { - return ProcResourceIdx == Other.ProcResourceIdx && Cycles == Other.Cycles; + return ProcResourceIdx == Other.ProcResourceIdx && Cycles == Other.Cycles && + StartAtCycle == Other.StartAtCycle; } }; diff --git a/llvm/include/llvm/Target/TargetSchedule.td b/llvm/include/llvm/Target/TargetSchedule.td --- a/llvm/include/llvm/Target/TargetSchedule.td +++ b/llvm/include/llvm/Target/TargetSchedule.td @@ -250,6 +250,7 @@ class ProcWriteResources resources> { list ProcResources = resources; list ResourceCycles = []; + list StartAtCycles = []; int Latency = 1; int NumMicroOps = 1; bit BeginGroup = false; diff --git a/llvm/test/TableGen/StartAtCycle.td b/llvm/test/TableGen/StartAtCycle.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/StartAtCycle.td @@ -0,0 +1,86 @@ +// RUN: llvm-tblgen -gen-subtarget -DCORRECT -I %p/../../include %s 2>&1 | \ +// RUN: FileCheck %s --check-prefix=CORRECT + +// RUN: not llvm-tblgen -gen-subtarget -DWRONG_SIZE -I %p/../../include %s 2>&1 | \ +// RUN: FileCheck %s --check-prefix=WRONG_SIZE + +// RUN: not llvm-tblgen -gen-subtarget -DWRONG_VALUE -I %p/../../include %s 2>&1 | \ +// RUN: FileCheck %s --check-prefix=WRONG_VALUE + +// RUN: not llvm-tblgen -gen-subtarget -DNEGATIVE_INVALID -I %p/../../include %s 2>&1 | \ +// RUN: FileCheck %s --check-prefix=NEGATIVE_INVALID + +// Make sure that StartAtCycle in WriteRes is used to generate the +// correct data. + +include "llvm/Target/Target.td" + +def MyTarget : Target; + +let BufferSize = 0 in { +def ResX0 : ProcResource<1>; // X0 +def ResX1 : ProcResource<1>; // X1 +def ResX2 : ProcResource<1>; // X2 +} + +let OutOperandList = (outs), InOperandList = (ins) in { + def Inst_A : Instruction; + def Inst_B : Instruction; +} + +let CompleteModel = 0 in { + def SchedModel_A: SchedMachineModel; +} + +def WriteInst_A : SchedWrite; +def WriteInst_B : SchedWrite; + +let SchedModel = SchedModel_A in { +// Check the generated data when there are no semantic issues. +#ifdef CORRECT +// CORRECT-LABEL: llvm::MCWriteProcResEntry MyTargetWriteProcResTable[] = { +// CORRECT-NEXT: { 0, 0, 0 }, // Invalid +def : WriteRes { +// CORRECT-NEXT: { 1, 2, 0}, // #1 +// CORRECT-NEXT: { 2, 4, 1}, // #2 +// CORRECT-NEXT: { 3, 3, 2}, // #3 + let ResourceCycles = [2, 4, 3]; + let StartAtCycles = [0, 1, 2]; +} +def : WriteRes { +// If unspecified, StartAtCycle is set to 0. +// CORRECT-NEXT: { 3, 1, 0} // #4 + let ResourceCycles = [1]; +} +#endif // CORRECT + +#ifdef WRONG_SIZE +// WRONG_SIZE: StartAtCycle.td:[[@LINE+1]]:1: error: Inconsistent resource cycles: size(StartAtCycles) != size(ProcResources): 2 vs 3 +def : WriteRes { + let ResourceCycles = [2, 4, 3]; + let StartAtCycles = [0, 1]; +} +#endif + +#ifdef WRONG_VALUE +// WRONG_VALUE: StartAtCycle.td:[[@LINE+1]]:1: error: Inconsistent resource cycles: StartAtCycles < Cycles must hold +def : WriteRes { + let ResourceCycles = [2, 4, 3]; + let StartAtCycles = [0, 1, 8]; +} +#endif + +#ifdef NEGATIVE_INVALID +// NEGATIVE_INVALID: StartAtCycle.td:[[@LINE+1]]:1: error: Invalid value: StartAtCycle must be a non-negative value. +def : WriteRes { + let ResourceCycles = [2]; + let StartAtCycles = [-1]; +} +#endif + +def : InstRW<[WriteInst_A], (instrs Inst_A)>; +def : InstRW<[WriteInst_B], (instrs Inst_B)>; +} + +def ProcessorA: ProcessorModel<"ProcessorA", SchedModel_A, []>; + diff --git a/llvm/tools/llvm-exegesis/lib/SchedClassResolution.cpp b/llvm/tools/llvm-exegesis/lib/SchedClassResolution.cpp --- a/llvm/tools/llvm-exegesis/lib/SchedClassResolution.cpp +++ b/llvm/tools/llvm-exegesis/lib/SchedClassResolution.cpp @@ -83,9 +83,14 @@ const MCWriteProcResEntry *WPR = Entry.second; const MCProcResourceDesc *const ProcResDesc = SM.getProcResource(WPR->ProcResourceIdx); + // TODO: Handle StartAtCycle in llvm-exegesis and llvm-mca. See + // https://github.com/llvm/llvm-project/issues/62680 and + // https://github.com/llvm/llvm-project/issues/62681 + assert(WPR->StartAtCycle == 0 && + "`llvm-exegesis` does not handle StartAtCycle > 0"); if (ProcResDesc->SubUnitsIdxBegin == nullptr) { // This is a ProcResUnit. - Result.push_back({WPR->ProcResourceIdx, WPR->Cycles}); + Result.push_back({WPR->ProcResourceIdx, WPR->Cycles, WPR->StartAtCycle}); ProcResUnitUsage[WPR->ProcResourceIdx] += WPR->Cycles; } else { // This is a ProcResGroup. First see if it contributes any cycles or if @@ -102,7 +107,8 @@ } // The ProcResGroup contributes `RemainingCycles` cycles of its own. Result.push_back({WPR->ProcResourceIdx, - static_cast(std::round(RemainingCycles))}); + static_cast(std::round(RemainingCycles)), + WPR->StartAtCycle}); // Spread the remaining cycles over all subunits. for (const auto *SubResIdx = ProcResDesc->SubUnitsIdxBegin; SubResIdx != ProcResDesc->SubUnitsIdxBegin + ProcResDesc->NumUnits; diff --git a/llvm/utils/TableGen/SubtargetEmitter.cpp b/llvm/utils/TableGen/SubtargetEmitter.cpp --- a/llvm/utils/TableGen/SubtargetEmitter.cpp +++ b/llvm/utils/TableGen/SubtargetEmitter.cpp @@ -111,6 +111,7 @@ Record *FindReadAdvance(const CodeGenSchedRW &SchedRead, const CodeGenProcModel &ProcModel); void ExpandProcResources(RecVec &PRVec, std::vector &Cycles, + std::vector &StartAtCycles, const CodeGenProcModel &ProcModel); void GenSchedClassTables(const CodeGenProcModel &ProcModel, SchedClassTables &SchedTables); @@ -968,6 +969,7 @@ // resource groups and super resources that cover them. void SubtargetEmitter::ExpandProcResources(RecVec &PRVec, std::vector &Cycles, + std::vector &StartAtCycles, const CodeGenProcModel &PM) { assert(PRVec.size() == Cycles.size() && "failed precondition"); for (unsigned i = 0, e = PRVec.size(); i != e; ++i) { @@ -990,6 +992,7 @@ SubDef->getLoc()); PRVec.push_back(SuperDef); Cycles.push_back(Cycles[i]); + StartAtCycles.push_back(StartAtCycles[i]); SubDef = SuperDef; } } @@ -1006,6 +1009,7 @@ if (SubI == SubE) { PRVec.push_back(PR); Cycles.push_back(Cycles[i]); + StartAtCycles.push_back(StartAtCycles[i]); } } } @@ -1140,22 +1144,48 @@ std::vector Cycles = WriteRes->getValueAsListOfInts("ResourceCycles"); - if (Cycles.empty()) { - // If ResourceCycles is not provided, default to one cycle per - // resource. - Cycles.resize(PRVec.size(), 1); - } else if (Cycles.size() != PRVec.size()) { + std::vector StartAtCycles = + WriteRes->getValueAsListOfInts("StartAtCycles"); + + // Check consistency of the two vectors carrying the start and + // stop cycles of the resources. + if (!Cycles.empty() && Cycles.size() != PRVec.size()) { // If ResourceCycles is provided, check consistency. PrintFatalError( WriteRes->getLoc(), - Twine("Inconsistent resource cycles: !size(ResourceCycles) != " - "!size(ProcResources): ") + Twine("Inconsistent resource cycles: size(ResourceCycles) != " + "size(ProcResources): ") .concat(Twine(PRVec.size())) .concat(" vs ") .concat(Twine(Cycles.size()))); } - ExpandProcResources(PRVec, Cycles, ProcModel); + if (!StartAtCycles.empty() && StartAtCycles.size() != PRVec.size()) { + PrintFatalError( + WriteRes->getLoc(), + Twine("Inconsistent resource cycles: size(StartAtCycles) != " + "size(ProcResources): ") + .concat(Twine(StartAtCycles.size())) + .concat(" vs ") + .concat(Twine(PRVec.size()))); + } + + if (Cycles.empty()) { + // If ResourceCycles is not provided, default to one cycle + // per resource. + Cycles.resize(PRVec.size(), 1); + } + + if (StartAtCycles.empty()) { + // If StartAtCycles is not provided, reserve the resource + // starting from cycle 0. + StartAtCycles.resize(PRVec.size(), 0); + } + + assert(StartAtCycles.size() == Cycles.size()); + + ExpandProcResources(PRVec, Cycles, StartAtCycles, ProcModel); + assert(StartAtCycles.size() == Cycles.size()); for (unsigned PRIdx = 0, PREnd = PRVec.size(); PRIdx != PREnd; ++PRIdx) { @@ -1163,6 +1193,17 @@ WPREntry.ProcResourceIdx = ProcModel.getProcResourceIdx(PRVec[PRIdx]); assert(WPREntry.ProcResourceIdx && "Bad ProcResourceIdx"); WPREntry.Cycles = Cycles[PRIdx]; + WPREntry.StartAtCycle = StartAtCycles[PRIdx]; + if (StartAtCycles[PRIdx] > Cycles[PRIdx]) { + PrintFatalError(WriteRes->getLoc(), + Twine("Inconsistent resource cycles: StartAtCycles " + "< Cycles must hold.")); + } + if (StartAtCycles[PRIdx] < 0) { + PrintFatalError(WriteRes->getLoc(), + Twine("Invalid value: StartAtCycle " + "must be a non-negative value.")); + } // If this resource is already used in this sequence, add the current // entry's cycles so that the same resource appears to be used // serially, rather than multiple parallel uses. This is important for @@ -1171,6 +1212,15 @@ for( ; WPRIdx != WPREnd; ++WPRIdx) { if (WriteProcResources[WPRIdx].ProcResourceIdx == WPREntry.ProcResourceIdx) { + // TODO: multiple use of the same resources would + // require either 1. thinking of how to handle multiple + // intervals for the same resource in + // `WriteProcResTable` (see + // `SubtargetEmitter::EmitSchedClassTables`), or + // 2. thinking how to merge multiple intervals into a + // single interval. + assert(WPREntry.StartAtCycle == 0 && + "multiple use ofthe same resource is not yet handled"); WriteProcResources[WPRIdx].Cycles += WPREntry.Cycles; break; } @@ -1275,15 +1325,16 @@ void SubtargetEmitter::EmitSchedClassTables(SchedClassTables &SchedTables, raw_ostream &OS) { // Emit global WriteProcResTable. - OS << "\n// {ProcResourceIdx, Cycles}\n" - << "extern const llvm::MCWriteProcResEntry " - << Target << "WriteProcResTable[] = {\n" - << " { 0, 0}, // Invalid\n"; + OS << "\n// {ProcResourceIdx, Cycles, StartAtCycle}\n" + << "extern const llvm::MCWriteProcResEntry " << Target + << "WriteProcResTable[] = {\n" + << " { 0, 0, 0 }, // Invalid\n"; for (unsigned WPRIdx = 1, WPREnd = SchedTables.WriteProcResources.size(); WPRIdx != WPREnd; ++WPRIdx) { MCWriteProcResEntry &WPREntry = SchedTables.WriteProcResources[WPRIdx]; OS << " {" << format("%2d", WPREntry.ProcResourceIdx) << ", " - << format("%2d", WPREntry.Cycles) << "}"; + << format("%2d", WPREntry.Cycles) << ", " + << format("%2d", WPREntry.StartAtCycle) << "}"; if (WPRIdx + 1 < WPREnd) OS << ','; OS << " // #" << WPRIdx << '\n';