blob: 210f3e247200a30977392f45864e1d3a88a950aa [file] [log] [blame]
//===- llvm/Target/TargetSchedule.cpp - Sched Machine Model ---------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements a wrapper around MCSchedModel that allows the interface
// to benefit from information currently only available in TargetInstrInfo.
//
//===----------------------------------------------------------------------===//
#include "llvm/CodeGen/TargetSchedule.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineOperand.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/MC/MCInstrDesc.h"
#include "llvm/MC/MCInstrItineraries.h"
#include "llvm/MC/MCSchedule.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cassert>
#include <numeric>
using namespace llvm;
static cl::opt<bool> ForceEnableIntervals(
"sched-model-force-enable-intervals", cl::Hidden, cl::init(false),
cl::desc("Force the use of resource intervals in the schedule model"));
bool TargetSchedModel::hasInstrSchedModel() const {
return EnableSchedModel && SchedModel.hasInstrSchedModel();
}
bool TargetSchedModel::hasInstrItineraries() const {
return EnableSchedItins && !InstrItins.isEmpty();
}
void TargetSchedModel::init(const TargetSubtargetInfo *TSInfo,
bool EnableSModel, bool EnableSItins) {
STI = TSInfo;
SchedModel = TSInfo->getSchedModel();
TII = TSInfo->getInstrInfo();
STI->initInstrItins(InstrItins);
EnableSchedModel = EnableSModel;
EnableSchedItins = EnableSItins;
unsigned NumRes = SchedModel.getNumProcResourceKinds();
ResourceFactors.resize(NumRes);
ResourceLCM = SchedModel.IssueWidth;
for (unsigned Idx = 0; Idx < NumRes; ++Idx) {
unsigned NumUnits = SchedModel.getProcResource(Idx)->NumUnits;
if (NumUnits > 0)
ResourceLCM = std::lcm(ResourceLCM, NumUnits);
}
MicroOpFactor = ResourceLCM / SchedModel.IssueWidth;
for (unsigned Idx = 0; Idx < NumRes; ++Idx) {
unsigned NumUnits = SchedModel.getProcResource(Idx)->NumUnits;
ResourceFactors[Idx] = NumUnits ? (ResourceLCM / NumUnits) : 0;
}
}
/// Returns true only if instruction is specified as single issue.
bool TargetSchedModel::mustBeginGroup(const MachineInstr *MI,
const MCSchedClassDesc *SC) const {
if (hasInstrSchedModel()) {
if (!SC)
SC = resolveSchedClass(MI);
if (SC->isValid())
return SC->BeginGroup;
}
return false;
}
bool TargetSchedModel::mustEndGroup(const MachineInstr *MI,
const MCSchedClassDesc *SC) const {
if (hasInstrSchedModel()) {
if (!SC)
SC = resolveSchedClass(MI);
if (SC->isValid())
return SC->EndGroup;
}
return false;
}
unsigned TargetSchedModel::getNumMicroOps(const MachineInstr *MI,
const MCSchedClassDesc *SC) const {
if (hasInstrItineraries()) {
int UOps = InstrItins.getNumMicroOps(MI->getDesc().getSchedClass());
return (UOps >= 0) ? UOps : TII->getNumMicroOps(&InstrItins, *MI);
}
if (hasInstrSchedModel()) {
if (!SC)
SC = resolveSchedClass(MI);
if (SC->isValid())
return SC->NumMicroOps;
}
return MI->isTransient() ? 0 : 1;
}
// The machine model may explicitly specify an invalid latency, which
// effectively means infinite latency. Since users of the TargetSchedule API
// don't know how to handle this, we convert it to a very large latency that is
// easy to distinguish when debugging the DAG but won't induce overflow.
static unsigned capLatency(int Cycles) {
return Cycles >= 0 ? Cycles : 1000;
}
/// Return the MCSchedClassDesc for this instruction. Some SchedClasses require
/// evaluation of predicates that depend on instruction operands or flags.
const MCSchedClassDesc *TargetSchedModel::
resolveSchedClass(const MachineInstr *MI) const {
// Get the definition's scheduling class descriptor from this machine model.
unsigned SchedClass = MI->getDesc().getSchedClass();
const MCSchedClassDesc *SCDesc = SchedModel.getSchedClassDesc(SchedClass);
if (!SCDesc->isValid())
return SCDesc;
#ifndef NDEBUG
unsigned NIter = 0;
#endif
while (SCDesc->isVariant()) {
assert(++NIter < 6 && "Variants are nested deeper than the magic number");
SchedClass = STI->resolveSchedClass(SchedClass, MI, this);
SCDesc = SchedModel.getSchedClassDesc(SchedClass);
}
return SCDesc;
}
// Top-level API for clients that know the operand indices. This doesn't need to
// return std::optional<unsigned>, as it always returns a valid latency.
unsigned TargetSchedModel::computeOperandLatency(
const MachineInstr *DefMI, unsigned DefOperIdx,
const MachineInstr *UseMI, unsigned UseOperIdx) const {
const unsigned InstrLatency = computeInstrLatency(DefMI);
const unsigned DefaultDefLatency = TII->defaultDefLatency(SchedModel, *DefMI);
if (!hasInstrSchedModel() && !hasInstrItineraries())
return DefaultDefLatency;
std::optional<unsigned> OperLatency;
if (hasInstrItineraries()) {
if (UseMI) {
OperLatency = TII->getOperandLatency(&InstrItins, *DefMI, DefOperIdx,
*UseMI, UseOperIdx);
}
else {
unsigned DefClass = DefMI->getDesc().getSchedClass();
OperLatency = InstrItins.getOperandCycle(DefClass, DefOperIdx);
}
// Expected latency is the max of InstrLatency and DefaultDefLatency, if we
// didn't find an operand latency.
return OperLatency ? *OperLatency
: std::max(InstrLatency, DefaultDefLatency);
}
// hasInstrSchedModel()
OperLatency =
TII->getOperandLatency(*this, DefMI, DefOperIdx, UseMI, UseOperIdx);
if (OperLatency)
return *OperLatency;
return DefMI->isTransient() ? 0 : DefaultDefLatency;
}
unsigned
TargetSchedModel::computeInstrLatency(const MCSchedClassDesc &SCDesc) const {
return capLatency(MCSchedModel::computeInstrLatency(*STI, SCDesc));
}
unsigned TargetSchedModel::computeInstrLatency(unsigned Opcode) const {
assert(hasInstrSchedModel() && "Only call this function with a SchedModel");
unsigned SCIdx = TII->get(Opcode).getSchedClass();
return capLatency(SchedModel.computeInstrLatency(*STI, SCIdx));
}
unsigned TargetSchedModel::computeInstrLatency(const MCInst &Inst) const {
if (hasInstrSchedModel())
return capLatency(SchedModel.computeInstrLatency(*STI, *TII, Inst));
return computeInstrLatency(Inst.getOpcode());
}
unsigned
TargetSchedModel::computeInstrLatency(const MachineInstr *MI,
bool UseDefaultDefLatency) const {
// For the itinerary model, fall back to the old subtarget hook.
// Allow subtargets to compute Bundle latencies outside the machine model.
if (hasInstrItineraries() || MI->isBundle() ||
(!hasInstrSchedModel() && !UseDefaultDefLatency))
return TII->getInstrLatency(&InstrItins, *MI);
std::optional<unsigned> InstrLatency;
// This is used by subtargets that define an InstrSchedModel.
InstrLatency = TII->getInstrLatency(*this, *MI);
return InstrLatency ? *InstrLatency : TII->defaultDefLatency(SchedModel, *MI);
}
unsigned TargetSchedModel::
computeOutputLatency(const MachineInstr *DefMI, unsigned DefOperIdx,
const MachineInstr *DepMI) const {
if (!SchedModel.isOutOfOrder())
return 1;
// Out-of-order processor can dispatch WAW dependencies in the same cycle.
// Treat predication as a data dependency for out-of-order cpus. In-order
// cpus do not need to treat predicated writes specially.
//
// TODO: The following hack exists because predication passes do not
// correctly append imp-use operands, and readsReg() strangely returns false
// for predicated defs.
Register Reg = DefMI->getOperand(DefOperIdx).getReg();
const MachineFunction &MF = *DefMI->getMF();
const TargetRegisterInfo *TRI = MF.getSubtarget().getRegisterInfo();
if (!DepMI->readsRegister(Reg, TRI) && TII->isPredicated(*DepMI))
return computeInstrLatency(DefMI);
// If we have a per operand scheduling model, check if this def is writing
// an unbuffered resource. If so, it treated like an in-order cpu.
if (hasInstrSchedModel()) {
const MCSchedClassDesc *SCDesc = resolveSchedClass(DefMI);
if (SCDesc->isValid()) {
for (const MCWriteProcResEntry *PRI = STI->getWriteProcResBegin(SCDesc),
*PRE = STI->getWriteProcResEnd(SCDesc); PRI != PRE; ++PRI) {
if (!SchedModel.getProcResource(PRI->ProcResourceIdx)->BufferSize)
return 1;
}
}
}
return 0;
}
double
TargetSchedModel::computeReciprocalThroughput(const MachineInstr *MI) const {
if (hasInstrItineraries()) {
unsigned SchedClass = MI->getDesc().getSchedClass();
return MCSchedModel::getReciprocalThroughput(SchedClass,
*getInstrItineraries());
}
if (hasInstrSchedModel())
return MCSchedModel::getReciprocalThroughput(*STI, *resolveSchedClass(MI));
return 0.0;
}
double
TargetSchedModel::computeReciprocalThroughput(unsigned Opcode) const {
unsigned SchedClass = TII->get(Opcode).getSchedClass();
if (hasInstrItineraries())
return MCSchedModel::getReciprocalThroughput(SchedClass,
*getInstrItineraries());
if (hasInstrSchedModel()) {
const MCSchedClassDesc &SCDesc = *SchedModel.getSchedClassDesc(SchedClass);
if (SCDesc.isValid() && !SCDesc.isVariant())
return MCSchedModel::getReciprocalThroughput(*STI, SCDesc);
}
return 0.0;
}
double
TargetSchedModel::computeReciprocalThroughput(const MCInst &MI) const {
if (hasInstrSchedModel())
return SchedModel.getReciprocalThroughput(*STI, *TII, MI);
return computeReciprocalThroughput(MI.getOpcode());
}
bool TargetSchedModel::enableIntervals() const {
if (ForceEnableIntervals)
return true;
return SchedModel.EnableIntervals;
}