| //===--------------------- InstrBuilder.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 the InstrBuilder interface. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "InstrBuilder.h" | |
| #include "llvm/ADT/APInt.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/MC/MCInst.h" | |
| #include "llvm/Support/Debug.h" | |
| #include "llvm/Support/WithColor.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #define DEBUG_TYPE "llvm-mca" | |
| namespace mca { | |
| using namespace llvm; | |
| static void initializeUsedResources(InstrDesc &ID, | |
| const MCSchedClassDesc &SCDesc, | |
| const MCSubtargetInfo &STI, | |
| ArrayRef<uint64_t> ProcResourceMasks) { | |
| const MCSchedModel &SM = STI.getSchedModel(); | |
| // Populate resources consumed. | |
| using ResourcePlusCycles = std::pair<uint64_t, ResourceUsage>; | |
| std::vector<ResourcePlusCycles> Worklist; | |
| // Track cycles contributed by resources that are in a "Super" relationship. | |
| // This is required if we want to correctly match the behavior of method | |
| // SubtargetEmitter::ExpandProcResource() in Tablegen. When computing the set | |
| // of "consumed" processor resources and resource cycles, the logic in | |
| // ExpandProcResource() doesn't update the number of resource cycles | |
| // contributed by a "Super" resource to a group. | |
| // We need to take this into account when we find that a processor resource is | |
| // part of a group, and it is also used as the "Super" of other resources. | |
| // This map stores the number of cycles contributed by sub-resources that are | |
| // part of a "Super" resource. The key value is the "Super" resource mask ID. | |
| DenseMap<uint64_t, unsigned> SuperResources; | |
| for (unsigned I = 0, E = SCDesc.NumWriteProcResEntries; I < E; ++I) { | |
| const MCWriteProcResEntry *PRE = STI.getWriteProcResBegin(&SCDesc) + I; | |
| const MCProcResourceDesc &PR = *SM.getProcResource(PRE->ProcResourceIdx); | |
| uint64_t Mask = ProcResourceMasks[PRE->ProcResourceIdx]; | |
| if (PR.BufferSize != -1) | |
| ID.Buffers.push_back(Mask); | |
| CycleSegment RCy(0, PRE->Cycles, false); | |
| Worklist.emplace_back(ResourcePlusCycles(Mask, ResourceUsage(RCy))); | |
| if (PR.SuperIdx) { | |
| uint64_t Super = ProcResourceMasks[PR.SuperIdx]; | |
| SuperResources[Super] += PRE->Cycles; | |
| } | |
| } | |
| // Sort elements by mask popcount, so that we prioritize resource units over | |
| // resource groups, and smaller groups over larger groups. | |
| llvm::sort(Worklist.begin(), Worklist.end(), | |
| [](const ResourcePlusCycles &A, const ResourcePlusCycles &B) { | |
| unsigned popcntA = countPopulation(A.first); | |
| unsigned popcntB = countPopulation(B.first); | |
| if (popcntA < popcntB) | |
| return true; | |
| if (popcntA > popcntB) | |
| return false; | |
| return A.first < B.first; | |
| }); | |
| uint64_t UsedResourceUnits = 0; | |
| // Remove cycles contributed by smaller resources. | |
| for (unsigned I = 0, E = Worklist.size(); I < E; ++I) { | |
| ResourcePlusCycles &A = Worklist[I]; | |
| if (!A.second.size()) { | |
| A.second.NumUnits = 0; | |
| A.second.setReserved(); | |
| ID.Resources.emplace_back(A); | |
| continue; | |
| } | |
| ID.Resources.emplace_back(A); | |
| uint64_t NormalizedMask = A.first; | |
| if (countPopulation(A.first) == 1) { | |
| UsedResourceUnits |= A.first; | |
| } else { | |
| // Remove the leading 1 from the resource group mask. | |
| NormalizedMask ^= PowerOf2Floor(NormalizedMask); | |
| } | |
| for (unsigned J = I + 1; J < E; ++J) { | |
| ResourcePlusCycles &B = Worklist[J]; | |
| if ((NormalizedMask & B.first) == NormalizedMask) { | |
| B.second.CS.Subtract(A.second.size() - SuperResources[A.first]); | |
| if (countPopulation(B.first) > 1) | |
| B.second.NumUnits++; | |
| } | |
| } | |
| } | |
| // A SchedWrite may specify a number of cycles in which a resource group | |
| // is reserved. For example (on target x86; cpu Haswell): | |
| // | |
| // SchedWriteRes<[HWPort0, HWPort1, HWPort01]> { | |
| // let ResourceCycles = [2, 2, 3]; | |
| // } | |
| // | |
| // This means: | |
| // Resource units HWPort0 and HWPort1 are both used for 2cy. | |
| // Resource group HWPort01 is the union of HWPort0 and HWPort1. | |
| // Since this write touches both HWPort0 and HWPort1 for 2cy, HWPort01 | |
| // will not be usable for 2 entire cycles from instruction issue. | |
| // | |
| // On top of those 2cy, SchedWriteRes explicitly specifies an extra latency | |
| // of 3 cycles for HWPort01. This tool assumes that the 3cy latency is an | |
| // extra delay on top of the 2 cycles latency. | |
| // During those extra cycles, HWPort01 is not usable by other instructions. | |
| for (ResourcePlusCycles &RPC : ID.Resources) { | |
| if (countPopulation(RPC.first) > 1 && !RPC.second.isReserved()) { | |
| // Remove the leading 1 from the resource group mask. | |
| uint64_t Mask = RPC.first ^ PowerOf2Floor(RPC.first); | |
| if ((Mask & UsedResourceUnits) == Mask) | |
| RPC.second.setReserved(); | |
| } | |
| } | |
| LLVM_DEBUG({ | |
| for (const std::pair<uint64_t, ResourceUsage> &R : ID.Resources) | |
| dbgs() << "\t\tMask=" << R.first << ", cy=" << R.second.size() << '\n'; | |
| for (const uint64_t R : ID.Buffers) | |
| dbgs() << "\t\tBuffer Mask=" << R << '\n'; | |
| }); | |
| } | |
| static void computeMaxLatency(InstrDesc &ID, const MCInstrDesc &MCDesc, | |
| const MCSchedClassDesc &SCDesc, | |
| const MCSubtargetInfo &STI) { | |
| if (MCDesc.isCall()) { | |
| // We cannot estimate how long this call will take. | |
| // Artificially set an arbitrarily high latency (100cy). | |
| ID.MaxLatency = 100U; | |
| return; | |
| } | |
| int Latency = MCSchedModel::computeInstrLatency(STI, SCDesc); | |
| // If latency is unknown, then conservatively assume a MaxLatency of 100cy. | |
| ID.MaxLatency = Latency < 0 ? 100U : static_cast<unsigned>(Latency); | |
| } | |
| void InstrBuilder::populateWrites(InstrDesc &ID, const MCInst &MCI, | |
| unsigned SchedClassID) { | |
| const MCInstrDesc &MCDesc = MCII.get(MCI.getOpcode()); | |
| const MCSchedModel &SM = STI.getSchedModel(); | |
| const MCSchedClassDesc &SCDesc = *SM.getSchedClassDesc(SchedClassID); | |
| // These are for now the (strong) assumptions made by this algorithm: | |
| // * The number of explicit and implicit register definitions in a MCInst | |
| // matches the number of explicit and implicit definitions according to | |
| // the opcode descriptor (MCInstrDesc). | |
| // * Register definitions take precedence over register uses in the operands | |
| // list. | |
| // * If an opcode specifies an optional definition, then the optional | |
| // definition is always the last operand in the sequence, and it can be | |
| // set to zero (i.e. "no register"). | |
| // | |
| // These assumptions work quite well for most out-of-order in-tree targets | |
| // like x86. This is mainly because the vast majority of instructions is | |
| // expanded to MCInst using a straightforward lowering logic that preserves | |
| // the ordering of the operands. | |
| unsigned NumExplicitDefs = MCDesc.getNumDefs(); | |
| unsigned NumImplicitDefs = MCDesc.getNumImplicitDefs(); | |
| unsigned NumWriteLatencyEntries = SCDesc.NumWriteLatencyEntries; | |
| unsigned TotalDefs = NumExplicitDefs + NumImplicitDefs; | |
| if (MCDesc.hasOptionalDef()) | |
| TotalDefs++; | |
| ID.Writes.resize(TotalDefs); | |
| // Iterate over the operands list, and skip non-register operands. | |
| // The first NumExplictDefs register operands are expected to be register | |
| // definitions. | |
| unsigned CurrentDef = 0; | |
| unsigned i = 0; | |
| for (; i < MCI.getNumOperands() && CurrentDef < NumExplicitDefs; ++i) { | |
| const MCOperand &Op = MCI.getOperand(i); | |
| if (!Op.isReg()) | |
| continue; | |
| WriteDescriptor &Write = ID.Writes[CurrentDef]; | |
| Write.OpIndex = i; | |
| if (CurrentDef < NumWriteLatencyEntries) { | |
| const MCWriteLatencyEntry &WLE = | |
| *STI.getWriteLatencyEntry(&SCDesc, CurrentDef); | |
| // Conservatively default to MaxLatency. | |
| Write.Latency = | |
| WLE.Cycles < 0 ? ID.MaxLatency : static_cast<unsigned>(WLE.Cycles); | |
| Write.SClassOrWriteResourceID = WLE.WriteResourceID; | |
| } else { | |
| // Assign a default latency for this write. | |
| Write.Latency = ID.MaxLatency; | |
| Write.SClassOrWriteResourceID = 0; | |
| } | |
| Write.IsOptionalDef = false; | |
| LLVM_DEBUG({ | |
| dbgs() << "\t\t[Def] OpIdx=" << Write.OpIndex | |
| << ", Latency=" << Write.Latency | |
| << ", WriteResourceID=" << Write.SClassOrWriteResourceID << '\n'; | |
| }); | |
| CurrentDef++; | |
| } | |
| if (CurrentDef != NumExplicitDefs) | |
| llvm::report_fatal_error( | |
| "error: Expected more register operand definitions. "); | |
| CurrentDef = 0; | |
| for (CurrentDef = 0; CurrentDef < NumImplicitDefs; ++CurrentDef) { | |
| unsigned Index = NumExplicitDefs + CurrentDef; | |
| WriteDescriptor &Write = ID.Writes[Index]; | |
| Write.OpIndex = ~CurrentDef; | |
| Write.RegisterID = MCDesc.getImplicitDefs()[CurrentDef]; | |
| if (Index < NumWriteLatencyEntries) { | |
| const MCWriteLatencyEntry &WLE = | |
| *STI.getWriteLatencyEntry(&SCDesc, Index); | |
| // Conservatively default to MaxLatency. | |
| Write.Latency = | |
| WLE.Cycles < 0 ? ID.MaxLatency : static_cast<unsigned>(WLE.Cycles); | |
| Write.SClassOrWriteResourceID = WLE.WriteResourceID; | |
| } else { | |
| // Assign a default latency for this write. | |
| Write.Latency = ID.MaxLatency; | |
| Write.SClassOrWriteResourceID = 0; | |
| } | |
| Write.IsOptionalDef = false; | |
| assert(Write.RegisterID != 0 && "Expected a valid phys register!"); | |
| LLVM_DEBUG({ | |
| dbgs() << "\t\t[Def] OpIdx=" << Write.OpIndex | |
| << ", PhysReg=" << MRI.getName(Write.RegisterID) | |
| << ", Latency=" << Write.Latency | |
| << ", WriteResourceID=" << Write.SClassOrWriteResourceID << '\n'; | |
| }); | |
| } | |
| if (MCDesc.hasOptionalDef()) { | |
| // Always assume that the optional definition is the last operand of the | |
| // MCInst sequence. | |
| const MCOperand &Op = MCI.getOperand(MCI.getNumOperands() - 1); | |
| if (i == MCI.getNumOperands() || !Op.isReg()) | |
| llvm::report_fatal_error( | |
| "error: expected a register operand for an optional " | |
| "definition. Instruction has not be correctly analyzed.\n", | |
| false); | |
| WriteDescriptor &Write = ID.Writes[TotalDefs - 1]; | |
| Write.OpIndex = MCI.getNumOperands() - 1; | |
| // Assign a default latency for this write. | |
| Write.Latency = ID.MaxLatency; | |
| Write.SClassOrWriteResourceID = 0; | |
| Write.IsOptionalDef = true; | |
| } | |
| } | |
| void InstrBuilder::populateReads(InstrDesc &ID, const MCInst &MCI, | |
| unsigned SchedClassID) { | |
| const MCInstrDesc &MCDesc = MCII.get(MCI.getOpcode()); | |
| unsigned NumExplicitDefs = MCDesc.getNumDefs(); | |
| // Skip explicit definitions. | |
| unsigned i = 0; | |
| for (; i < MCI.getNumOperands() && NumExplicitDefs; ++i) { | |
| const MCOperand &Op = MCI.getOperand(i); | |
| if (Op.isReg()) | |
| NumExplicitDefs--; | |
| } | |
| if (NumExplicitDefs) | |
| llvm::report_fatal_error( | |
| "error: Expected more register operand definitions. ", false); | |
| unsigned NumExplicitUses = MCI.getNumOperands() - i; | |
| unsigned NumImplicitUses = MCDesc.getNumImplicitUses(); | |
| if (MCDesc.hasOptionalDef()) { | |
| assert(NumExplicitUses); | |
| NumExplicitUses--; | |
| } | |
| unsigned TotalUses = NumExplicitUses + NumImplicitUses; | |
| if (!TotalUses) | |
| return; | |
| ID.Reads.resize(TotalUses); | |
| for (unsigned CurrentUse = 0; CurrentUse < NumExplicitUses; ++CurrentUse) { | |
| ReadDescriptor &Read = ID.Reads[CurrentUse]; | |
| Read.OpIndex = i + CurrentUse; | |
| Read.UseIndex = CurrentUse; | |
| Read.SchedClassID = SchedClassID; | |
| LLVM_DEBUG(dbgs() << "\t\t[Use] OpIdx=" << Read.OpIndex | |
| << ", UseIndex=" << Read.UseIndex << '\n'); | |
| } | |
| for (unsigned CurrentUse = 0; CurrentUse < NumImplicitUses; ++CurrentUse) { | |
| ReadDescriptor &Read = ID.Reads[NumExplicitUses + CurrentUse]; | |
| Read.OpIndex = ~CurrentUse; | |
| Read.UseIndex = NumExplicitUses + CurrentUse; | |
| Read.RegisterID = MCDesc.getImplicitUses()[CurrentUse]; | |
| Read.SchedClassID = SchedClassID; | |
| LLVM_DEBUG(dbgs() << "\t\t[Use] OpIdx=" << Read.OpIndex << ", RegisterID=" | |
| << MRI.getName(Read.RegisterID) << '\n'); | |
| } | |
| } | |
| const InstrDesc &InstrBuilder::createInstrDescImpl(const MCInst &MCI) { | |
| assert(STI.getSchedModel().hasInstrSchedModel() && | |
| "Itineraries are not yet supported!"); | |
| // Obtain the instruction descriptor from the opcode. | |
| unsigned short Opcode = MCI.getOpcode(); | |
| const MCInstrDesc &MCDesc = MCII.get(Opcode); | |
| const MCSchedModel &SM = STI.getSchedModel(); | |
| // Then obtain the scheduling class information from the instruction. | |
| unsigned SchedClassID = MCDesc.getSchedClass(); | |
| unsigned CPUID = SM.getProcessorID(); | |
| // Try to solve variant scheduling classes. | |
| if (SchedClassID) { | |
| while (SchedClassID && SM.getSchedClassDesc(SchedClassID)->isVariant()) | |
| SchedClassID = STI.resolveVariantSchedClass(SchedClassID, &MCI, CPUID); | |
| if (!SchedClassID) | |
| llvm::report_fatal_error("unable to resolve this variant class."); | |
| } | |
| // Check if this instruction is supported. Otherwise, report a fatal error. | |
| const MCSchedClassDesc &SCDesc = *SM.getSchedClassDesc(SchedClassID); | |
| if (SCDesc.NumMicroOps == MCSchedClassDesc::InvalidNumMicroOps) { | |
| std::string ToString; | |
| llvm::raw_string_ostream OS(ToString); | |
| WithColor::error() << "found an unsupported instruction in the input" | |
| << " assembly sequence.\n"; | |
| MCIP.printInst(&MCI, OS, "", STI); | |
| OS.flush(); | |
| WithColor::note() << "instruction: " << ToString << '\n'; | |
| llvm::report_fatal_error( | |
| "Don't know how to analyze unsupported instructions."); | |
| } | |
| // Create a new empty descriptor. | |
| std::unique_ptr<InstrDesc> ID = llvm::make_unique<InstrDesc>(); | |
| ID->NumMicroOps = SCDesc.NumMicroOps; | |
| if (MCDesc.isCall()) { | |
| // We don't correctly model calls. | |
| WithColor::warning() << "found a call in the input assembly sequence.\n"; | |
| WithColor::note() << "call instructions are not correctly modeled. " | |
| << "Assume a latency of 100cy.\n"; | |
| } | |
| if (MCDesc.isReturn()) { | |
| WithColor::warning() << "found a return instruction in the input" | |
| << " assembly sequence.\n"; | |
| WithColor::note() << "program counter updates are ignored.\n"; | |
| } | |
| ID->MayLoad = MCDesc.mayLoad(); | |
| ID->MayStore = MCDesc.mayStore(); | |
| ID->HasSideEffects = MCDesc.hasUnmodeledSideEffects(); | |
| initializeUsedResources(*ID, SCDesc, STI, ProcResourceMasks); | |
| computeMaxLatency(*ID, MCDesc, SCDesc, STI); | |
| populateWrites(*ID, MCI, SchedClassID); | |
| populateReads(*ID, MCI, SchedClassID); | |
| LLVM_DEBUG(dbgs() << "\t\tMaxLatency=" << ID->MaxLatency << '\n'); | |
| LLVM_DEBUG(dbgs() << "\t\tNumMicroOps=" << ID->NumMicroOps << '\n'); | |
| // Now add the new descriptor. | |
| SchedClassID = MCDesc.getSchedClass(); | |
| if (!SM.getSchedClassDesc(SchedClassID)->isVariant()) { | |
| Descriptors[MCI.getOpcode()] = std::move(ID); | |
| return *Descriptors[MCI.getOpcode()]; | |
| } | |
| VariantDescriptors[&MCI] = std::move(ID); | |
| return *VariantDescriptors[&MCI]; | |
| } | |
| const InstrDesc &InstrBuilder::getOrCreateInstrDesc(const MCInst &MCI) { | |
| if (Descriptors.find_as(MCI.getOpcode()) != Descriptors.end()) | |
| return *Descriptors[MCI.getOpcode()]; | |
| if (VariantDescriptors.find(&MCI) != VariantDescriptors.end()) | |
| return *VariantDescriptors[&MCI]; | |
| return createInstrDescImpl(MCI); | |
| } | |
| std::unique_ptr<Instruction> | |
| InstrBuilder::createInstruction(const MCInst &MCI) { | |
| const InstrDesc &D = getOrCreateInstrDesc(MCI); | |
| std::unique_ptr<Instruction> NewIS = llvm::make_unique<Instruction>(D); | |
| // Initialize Reads first. | |
| for (const ReadDescriptor &RD : D.Reads) { | |
| int RegID = -1; | |
| if (!RD.isImplicitRead()) { | |
| // explicit read. | |
| const MCOperand &Op = MCI.getOperand(RD.OpIndex); | |
| // Skip non-register operands. | |
| if (!Op.isReg()) | |
| continue; | |
| RegID = Op.getReg(); | |
| } else { | |
| // Implicit read. | |
| RegID = RD.RegisterID; | |
| } | |
| // Skip invalid register operands. | |
| if (!RegID) | |
| continue; | |
| // Okay, this is a register operand. Create a ReadState for it. | |
| assert(RegID > 0 && "Invalid register ID found!"); | |
| NewIS->getUses().emplace_back(llvm::make_unique<ReadState>(RD, RegID)); | |
| } | |
| // Early exit if there are no writes. | |
| if (D.Writes.empty()) | |
| return NewIS; | |
| // Track register writes that implicitly clear the upper portion of the | |
| // underlying super-registers using an APInt. | |
| APInt WriteMask(D.Writes.size(), 0); | |
| // Now query the MCInstrAnalysis object to obtain information about which | |
| // register writes implicitly clear the upper portion of a super-register. | |
| MCIA.clearsSuperRegisters(MRI, MCI, WriteMask); | |
| // Check if this is a dependency breaking instruction. | |
| if (MCIA.isDependencyBreaking(STI, MCI)) | |
| NewIS->setDependencyBreaking(); | |
| // Initialize writes. | |
| unsigned WriteIndex = 0; | |
| for (const WriteDescriptor &WD : D.Writes) { | |
| unsigned RegID = WD.isImplicitWrite() ? WD.RegisterID | |
| : MCI.getOperand(WD.OpIndex).getReg(); | |
| // Check if this is a optional definition that references NoReg. | |
| if (WD.IsOptionalDef && !RegID) { | |
| ++WriteIndex; | |
| continue; | |
| } | |
| assert(RegID && "Expected a valid register ID!"); | |
| NewIS->getDefs().emplace_back(llvm::make_unique<WriteState>( | |
| WD, RegID, /* ClearsSuperRegs */ WriteMask[WriteIndex])); | |
| ++WriteIndex; | |
| } | |
| return NewIS; | |
| } | |
| } // namespace mca |