//===--------------------- 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