| //===--------------------- InstrBuilder.cpp ---------------------*- C++ -*-===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| /// \file |
| /// |
| /// This file implements the InstrBuilder interface. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/MCA/InstrBuilder.h" |
| #include "llvm/ADT/APInt.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/Statistic.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-instrbuilder" |
| |
| namespace llvm { |
| namespace mca { |
| |
| char RecycledInstErr::ID = 0; |
| |
| InstrBuilder::InstrBuilder(const llvm::MCSubtargetInfo &sti, |
| const llvm::MCInstrInfo &mcii, |
| const llvm::MCRegisterInfo &mri, |
| const llvm::MCInstrAnalysis *mcia, |
| const mca::InstrumentManager &im) |
| : STI(sti), MCII(mcii), MRI(mri), MCIA(mcia), IM(im), FirstCallInst(true), |
| FirstReturnInst(true) { |
| const MCSchedModel &SM = STI.getSchedModel(); |
| ProcResourceMasks.resize(SM.getNumProcResourceKinds()); |
| computeProcResourceMasks(STI.getSchedModel(), ProcResourceMasks); |
| } |
| |
| 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>; |
| SmallVector<ResourcePlusCycles, 4> 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; |
| |
| unsigned NumProcResources = SM.getNumProcResourceKinds(); |
| APInt Buffers(NumProcResources, 0); |
| |
| bool AllInOrderResources = true; |
| bool AnyDispatchHazards = false; |
| for (unsigned I = 0, E = SCDesc.NumWriteProcResEntries; I < E; ++I) { |
| const MCWriteProcResEntry *PRE = STI.getWriteProcResBegin(&SCDesc) + I; |
| const MCProcResourceDesc &PR = *SM.getProcResource(PRE->ProcResourceIdx); |
| if (!PRE->Cycles) { |
| #ifndef NDEBUG |
| WithColor::warning() |
| << "Ignoring invalid write of zero cycles on processor resource " |
| << PR.Name << "\n"; |
| WithColor::note() << "found in scheduling class " << SCDesc.Name |
| << " (write index #" << I << ")\n"; |
| #endif |
| continue; |
| } |
| |
| uint64_t Mask = ProcResourceMasks[PRE->ProcResourceIdx]; |
| if (PR.BufferSize < 0) { |
| AllInOrderResources = false; |
| } else { |
| Buffers.setBit(getResourceStateIndex(Mask)); |
| AnyDispatchHazards |= (PR.BufferSize == 0); |
| AllInOrderResources &= (PR.BufferSize <= 1); |
| } |
| |
| 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; |
| } |
| } |
| |
| ID.MustIssueImmediately = AllInOrderResources && AnyDispatchHazards; |
| |
| // Sort elements by mask popcount, so that we prioritize resource units over |
| // resource groups, and smaller groups over larger groups. |
| sort(Worklist, [](const ResourcePlusCycles &A, const ResourcePlusCycles &B) { |
| unsigned popcntA = llvm::popcount(A.first); |
| unsigned popcntB = llvm::popcount(B.first); |
| if (popcntA < popcntB) |
| return true; |
| if (popcntA > popcntB) |
| return false; |
| return A.first < B.first; |
| }); |
| |
| uint64_t UsedResourceUnits = 0; |
| uint64_t UsedResourceGroups = 0; |
| uint64_t UnitsFromResourceGroups = 0; |
| |
| // Remove cycles contributed by smaller resources, and check if there |
| // are partially overlapping resource groups. |
| ID.HasPartiallyOverlappingGroups = false; |
| |
| for (unsigned I = 0, E = Worklist.size(); I < E; ++I) { |
| ResourcePlusCycles &A = Worklist[I]; |
| if (!A.second.size()) { |
| assert(llvm::popcount(A.first) > 1 && "Expected a group!"); |
| UsedResourceGroups |= PowerOf2Floor(A.first); |
| continue; |
| } |
| |
| ID.Resources.emplace_back(A); |
| uint64_t NormalizedMask = A.first; |
| |
| if (llvm::popcount(A.first) == 1) { |
| UsedResourceUnits |= A.first; |
| } else { |
| // Remove the leading 1 from the resource group mask. |
| NormalizedMask ^= PowerOf2Floor(NormalizedMask); |
| if (UnitsFromResourceGroups & NormalizedMask) |
| ID.HasPartiallyOverlappingGroups = true; |
| |
| UnitsFromResourceGroups |= NormalizedMask; |
| UsedResourceGroups |= (A.first ^ 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 (llvm::popcount(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 (llvm::popcount(RPC.first) > 1 && !RPC.second.isReserved()) { |
| // Remove the leading 1 from the resource group mask. |
| uint64_t Mask = RPC.first ^ PowerOf2Floor(RPC.first); |
| uint64_t MaxResourceUnits = llvm::popcount(Mask); |
| if (RPC.second.NumUnits > (unsigned)llvm::popcount(Mask)) { |
| RPC.second.setReserved(); |
| RPC.second.NumUnits = MaxResourceUnits; |
| } |
| } |
| } |
| |
| // Identify extra buffers that are consumed through super resources. |
| for (const std::pair<uint64_t, unsigned> &SR : SuperResources) { |
| for (unsigned I = 1, E = NumProcResources; I < E; ++I) { |
| const MCProcResourceDesc &PR = *SM.getProcResource(I); |
| if (PR.BufferSize == -1) |
| continue; |
| |
| uint64_t Mask = ProcResourceMasks[I]; |
| if (Mask != SR.first && ((Mask & SR.first) == SR.first)) |
| Buffers.setBit(getResourceStateIndex(Mask)); |
| } |
| } |
| |
| ID.UsedBuffers = Buffers.getZExtValue(); |
| ID.UsedProcResUnits = UsedResourceUnits; |
| ID.UsedProcResGroups = UsedResourceGroups; |
| |
| LLVM_DEBUG({ |
| for (const std::pair<uint64_t, ResourceUsage> &R : ID.Resources) |
| dbgs() << "\t\tResource Mask=" << format_hex(R.first, 16) << ", " |
| << "Reserved=" << R.second.isReserved() << ", " |
| << "#Units=" << R.second.NumUnits << ", " |
| << "cy=" << R.second.size() << '\n'; |
| uint64_t BufferIDs = ID.UsedBuffers; |
| while (BufferIDs) { |
| uint64_t Current = BufferIDs & (-BufferIDs); |
| dbgs() << "\t\tBuffer Mask=" << format_hex(Current, 16) << '\n'; |
| BufferIDs ^= Current; |
| } |
| dbgs() << "\t\t Used Units=" << format_hex(ID.UsedProcResUnits, 16) << '\n'; |
| dbgs() << "\t\tUsed Groups=" << format_hex(ID.UsedProcResGroups, 16) |
| << '\n'; |
| dbgs() << "\t\tHasPartiallyOverlappingGroups=" |
| << ID.HasPartiallyOverlappingGroups << '\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); |
| } |
| |
| static Error verifyOperands(const MCInstrDesc &MCDesc, const MCInst &MCI) { |
| // Count register definitions, and skip non register operands in the process. |
| unsigned I, E; |
| unsigned NumExplicitDefs = MCDesc.getNumDefs(); |
| for (I = 0, E = MCI.getNumOperands(); NumExplicitDefs && I < E; ++I) { |
| const MCOperand &Op = MCI.getOperand(I); |
| if (Op.isReg()) |
| --NumExplicitDefs; |
| } |
| |
| if (NumExplicitDefs) { |
| return make_error<InstructionError<MCInst>>( |
| "Expected more register operand definitions.", MCI); |
| } |
| |
| if (MCDesc.hasOptionalDef()) { |
| // Always assume that the optional definition is the last operand. |
| const MCOperand &Op = MCI.getOperand(MCDesc.getNumOperands() - 1); |
| if (I == MCI.getNumOperands() || !Op.isReg()) { |
| std::string Message = |
| "expected a register operand for an optional definition. Instruction " |
| "has not been correctly analyzed."; |
| return make_error<InstructionError<MCInst>>(Message, MCI); |
| } |
| } |
| |
| return ErrorSuccess(); |
| } |
| |
| 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); |
| |
| // Assumptions made by this algorithm: |
| // 1. 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). |
| // 2. Uses start at index #(MCDesc.getNumDefs()). |
| // 3. There can only be a single optional register definition, an it is |
| // either the last operand of the sequence (excluding extra operands |
| // contributed by variadic opcodes) or one of the explicit register |
| // definitions. The latter occurs for some Thumb1 instructions. |
| // |
| // 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. |
| // |
| // About assumption 1. |
| // The algorithm allows non-register operands between register operand |
| // definitions. This helps to handle some special ARM instructions with |
| // implicit operand increment (-mtriple=armv7): |
| // |
| // vld1.32 {d18, d19}, [r1]! @ <MCInst #1463 VLD1q32wb_fixed |
| // @ <MCOperand Reg:59> |
| // @ <MCOperand Imm:0> (!!) |
| // @ <MCOperand Reg:67> |
| // @ <MCOperand Imm:0> |
| // @ <MCOperand Imm:14> |
| // @ <MCOperand Reg:0>> |
| // |
| // MCDesc reports: |
| // 6 explicit operands. |
| // 1 optional definition |
| // 2 explicit definitions (!!) |
| // |
| // The presence of an 'Imm' operand between the two register definitions |
| // breaks the assumption that "register definitions are always at the |
| // beginning of the operand sequence". |
| // |
| // To workaround this issue, this algorithm ignores (i.e. skips) any |
| // non-register operands between register definitions. The optional |
| // definition is still at index #(NumOperands-1). |
| // |
| // According to assumption 2. register reads start at #(NumExplicitDefs-1). |
| // That means, register R1 from the example is both read and written. |
| unsigned NumExplicitDefs = MCDesc.getNumDefs(); |
| unsigned NumImplicitDefs = MCDesc.implicit_defs().size(); |
| unsigned NumWriteLatencyEntries = SCDesc.NumWriteLatencyEntries; |
| unsigned TotalDefs = NumExplicitDefs + NumImplicitDefs; |
| if (MCDesc.hasOptionalDef()) |
| TotalDefs++; |
| |
| unsigned NumVariadicOps = MCI.getNumOperands() - MCDesc.getNumOperands(); |
| ID.Writes.resize(TotalDefs + NumVariadicOps); |
| // Iterate over the operands list, and skip non-register operands. |
| // The first NumExplicitDefs register operands are expected to be register |
| // definitions. |
| unsigned CurrentDef = 0; |
| unsigned OptionalDefIdx = MCDesc.getNumOperands() - 1; |
| unsigned i = 0; |
| for (; i < MCI.getNumOperands() && CurrentDef < NumExplicitDefs; ++i) { |
| const MCOperand &Op = MCI.getOperand(i); |
| if (!Op.isReg()) |
| continue; |
| |
| if (MCDesc.operands()[CurrentDef].isOptionalDef()) { |
| OptionalDefIdx = CurrentDef++; |
| 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++; |
| } |
| |
| assert(CurrentDef == NumExplicitDefs && |
| "Expected more register operand definitions."); |
| for (CurrentDef = 0; CurrentDef < NumImplicitDefs; ++CurrentDef) { |
| unsigned Index = NumExplicitDefs + CurrentDef; |
| WriteDescriptor &Write = ID.Writes[Index]; |
| Write.OpIndex = ~CurrentDef; |
| Write.RegisterID = MCDesc.implicit_defs()[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][I] OpIdx=" << ~Write.OpIndex |
| << ", PhysReg=" << MRI.getName(Write.RegisterID) |
| << ", Latency=" << Write.Latency |
| << ", WriteResourceID=" << Write.SClassOrWriteResourceID << '\n'; |
| }); |
| } |
| |
| if (MCDesc.hasOptionalDef()) { |
| WriteDescriptor &Write = ID.Writes[NumExplicitDefs + NumImplicitDefs]; |
| Write.OpIndex = OptionalDefIdx; |
| // Assign a default latency for this write. |
| Write.Latency = ID.MaxLatency; |
| Write.SClassOrWriteResourceID = 0; |
| Write.IsOptionalDef = true; |
| LLVM_DEBUG({ |
| dbgs() << "\t\t[Def][O] OpIdx=" << Write.OpIndex |
| << ", Latency=" << Write.Latency |
| << ", WriteResourceID=" << Write.SClassOrWriteResourceID << '\n'; |
| }); |
| } |
| |
| if (!NumVariadicOps) |
| return; |
| |
| bool AssumeUsesOnly = !MCDesc.variadicOpsAreDefs(); |
| CurrentDef = NumExplicitDefs + NumImplicitDefs + MCDesc.hasOptionalDef(); |
| for (unsigned I = 0, OpIndex = MCDesc.getNumOperands(); |
| I < NumVariadicOps && !AssumeUsesOnly; ++I, ++OpIndex) { |
| const MCOperand &Op = MCI.getOperand(OpIndex); |
| if (!Op.isReg()) |
| continue; |
| |
| WriteDescriptor &Write = ID.Writes[CurrentDef]; |
| Write.OpIndex = OpIndex; |
| // Assign a default latency for this write. |
| Write.Latency = ID.MaxLatency; |
| Write.SClassOrWriteResourceID = 0; |
| Write.IsOptionalDef = false; |
| ++CurrentDef; |
| LLVM_DEBUG({ |
| dbgs() << "\t\t[Def][V] OpIdx=" << Write.OpIndex |
| << ", Latency=" << Write.Latency |
| << ", WriteResourceID=" << Write.SClassOrWriteResourceID << '\n'; |
| }); |
| } |
| |
| ID.Writes.resize(CurrentDef); |
| } |
| |
| void InstrBuilder::populateReads(InstrDesc &ID, const MCInst &MCI, |
| unsigned SchedClassID) { |
| const MCInstrDesc &MCDesc = MCII.get(MCI.getOpcode()); |
| unsigned NumExplicitUses = MCDesc.getNumOperands() - MCDesc.getNumDefs(); |
| unsigned NumImplicitUses = MCDesc.implicit_uses().size(); |
| // Remove the optional definition. |
| if (MCDesc.hasOptionalDef()) |
| --NumExplicitUses; |
| unsigned NumVariadicOps = MCI.getNumOperands() - MCDesc.getNumOperands(); |
| unsigned TotalUses = NumExplicitUses + NumImplicitUses + NumVariadicOps; |
| ID.Reads.resize(TotalUses); |
| unsigned CurrentUse = 0; |
| for (unsigned I = 0, OpIndex = MCDesc.getNumDefs(); I < NumExplicitUses; |
| ++I, ++OpIndex) { |
| const MCOperand &Op = MCI.getOperand(OpIndex); |
| if (!Op.isReg()) |
| continue; |
| |
| ReadDescriptor &Read = ID.Reads[CurrentUse]; |
| Read.OpIndex = OpIndex; |
| Read.UseIndex = I; |
| Read.SchedClassID = SchedClassID; |
| ++CurrentUse; |
| LLVM_DEBUG(dbgs() << "\t\t[Use] OpIdx=" << Read.OpIndex |
| << ", UseIndex=" << Read.UseIndex << '\n'); |
| } |
| |
| // For the purpose of ReadAdvance, implicit uses come directly after explicit |
| // uses. The "UseIndex" must be updated according to that implicit layout. |
| for (unsigned I = 0; I < NumImplicitUses; ++I) { |
| ReadDescriptor &Read = ID.Reads[CurrentUse + I]; |
| Read.OpIndex = ~I; |
| Read.UseIndex = NumExplicitUses + I; |
| Read.RegisterID = MCDesc.implicit_uses()[I]; |
| Read.SchedClassID = SchedClassID; |
| LLVM_DEBUG(dbgs() << "\t\t[Use][I] OpIdx=" << ~Read.OpIndex |
| << ", UseIndex=" << Read.UseIndex << ", RegisterID=" |
| << MRI.getName(Read.RegisterID) << '\n'); |
| } |
| |
| CurrentUse += NumImplicitUses; |
| |
| bool AssumeDefsOnly = MCDesc.variadicOpsAreDefs(); |
| for (unsigned I = 0, OpIndex = MCDesc.getNumOperands(); |
| I < NumVariadicOps && !AssumeDefsOnly; ++I, ++OpIndex) { |
| const MCOperand &Op = MCI.getOperand(OpIndex); |
| if (!Op.isReg()) |
| continue; |
| |
| ReadDescriptor &Read = ID.Reads[CurrentUse]; |
| Read.OpIndex = OpIndex; |
| Read.UseIndex = NumExplicitUses + NumImplicitUses + I; |
| Read.SchedClassID = SchedClassID; |
| ++CurrentUse; |
| LLVM_DEBUG(dbgs() << "\t\t[Use][V] OpIdx=" << Read.OpIndex |
| << ", UseIndex=" << Read.UseIndex << '\n'); |
| } |
| |
| ID.Reads.resize(CurrentUse); |
| } |
| |
| Error InstrBuilder::verifyInstrDesc(const InstrDesc &ID, |
| const MCInst &MCI) const { |
| if (ID.NumMicroOps != 0) |
| return ErrorSuccess(); |
| |
| bool UsesBuffers = ID.UsedBuffers; |
| bool UsesResources = !ID.Resources.empty(); |
| if (!UsesBuffers && !UsesResources) |
| return ErrorSuccess(); |
| |
| // FIXME: see PR44797. We should revisit these checks and possibly move them |
| // in CodeGenSchedule.cpp. |
| StringRef Message = "found an inconsistent instruction that decodes to zero " |
| "opcodes and that consumes scheduler resources."; |
| return make_error<InstructionError<MCInst>>(std::string(Message), MCI); |
| } |
| |
| Expected<const InstrDesc &> |
| InstrBuilder::createInstrDescImpl(const MCInst &MCI, |
| const SmallVector<SharedInstrument> &IVec) { |
| 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. |
| // Allow InstrumentManager to override and use a different SchedClassID |
| unsigned SchedClassID = IM.getSchedClassID(MCII, MCI, IVec); |
| bool IsVariant = SM.getSchedClassDesc(SchedClassID)->isVariant(); |
| |
| // Try to solve variant scheduling classes. |
| if (IsVariant) { |
| unsigned CPUID = SM.getProcessorID(); |
| while (SchedClassID && SM.getSchedClassDesc(SchedClassID)->isVariant()) |
| SchedClassID = |
| STI.resolveVariantSchedClass(SchedClassID, &MCI, &MCII, CPUID); |
| |
| if (!SchedClassID) { |
| return make_error<InstructionError<MCInst>>( |
| "unable to resolve scheduling class for write variant.", MCI); |
| } |
| } |
| |
| // Check if this instruction is supported. Otherwise, report an error. |
| const MCSchedClassDesc &SCDesc = *SM.getSchedClassDesc(SchedClassID); |
| if (SCDesc.NumMicroOps == MCSchedClassDesc::InvalidNumMicroOps) { |
| return make_error<InstructionError<MCInst>>( |
| "found an unsupported instruction in the input assembly sequence.", |
| MCI); |
| } |
| |
| LLVM_DEBUG(dbgs() << "\n\t\tOpcode Name= " << MCII.getName(Opcode) << '\n'); |
| LLVM_DEBUG(dbgs() << "\t\tSchedClassID=" << SchedClassID << '\n'); |
| LLVM_DEBUG(dbgs() << "\t\tOpcode=" << Opcode << '\n'); |
| |
| // Create a new empty descriptor. |
| std::unique_ptr<InstrDesc> ID = std::make_unique<InstrDesc>(); |
| ID->NumMicroOps = SCDesc.NumMicroOps; |
| ID->SchedClassID = SchedClassID; |
| |
| if (MCDesc.isCall() && FirstCallInst) { |
| // 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"; |
| FirstCallInst = false; |
| } |
| |
| if (MCDesc.isReturn() && FirstReturnInst) { |
| WithColor::warning() << "found a return instruction in the input" |
| << " assembly sequence.\n"; |
| WithColor::note() << "program counter updates are ignored.\n"; |
| FirstReturnInst = false; |
| } |
| |
| initializeUsedResources(*ID, SCDesc, STI, ProcResourceMasks); |
| computeMaxLatency(*ID, MCDesc, SCDesc, STI); |
| |
| if (Error Err = verifyOperands(MCDesc, MCI)) |
| return std::move(Err); |
| |
| populateWrites(*ID, MCI, SchedClassID); |
| populateReads(*ID, MCI, SchedClassID); |
| |
| LLVM_DEBUG(dbgs() << "\t\tMaxLatency=" << ID->MaxLatency << '\n'); |
| LLVM_DEBUG(dbgs() << "\t\tNumMicroOps=" << ID->NumMicroOps << '\n'); |
| |
| // Validation check on the instruction descriptor. |
| if (Error Err = verifyInstrDesc(*ID, MCI)) |
| return std::move(Err); |
| |
| // Now add the new descriptor. |
| bool IsVariadic = MCDesc.isVariadic(); |
| if ((ID->IsRecyclable = !IsVariadic && !IsVariant)) { |
| auto DKey = std::make_pair(MCI.getOpcode(), SchedClassID); |
| Descriptors[DKey] = std::move(ID); |
| return *Descriptors[DKey]; |
| } |
| |
| auto VDKey = std::make_pair(&MCI, SchedClassID); |
| VariantDescriptors[VDKey] = std::move(ID); |
| return *VariantDescriptors[VDKey]; |
| } |
| |
| Expected<const InstrDesc &> |
| InstrBuilder::getOrCreateInstrDesc(const MCInst &MCI, |
| const SmallVector<SharedInstrument> &IVec) { |
| // Cache lookup using SchedClassID from Instrumentation |
| unsigned SchedClassID = IM.getSchedClassID(MCII, MCI, IVec); |
| |
| auto DKey = std::make_pair(MCI.getOpcode(), SchedClassID); |
| if (Descriptors.find_as(DKey) != Descriptors.end()) |
| return *Descriptors[DKey]; |
| |
| unsigned CPUID = STI.getSchedModel().getProcessorID(); |
| SchedClassID = STI.resolveVariantSchedClass(SchedClassID, &MCI, &MCII, CPUID); |
| auto VDKey = std::make_pair(&MCI, SchedClassID); |
| if (VariantDescriptors.find(VDKey) != VariantDescriptors.end()) |
| return *VariantDescriptors[VDKey]; |
| |
| return createInstrDescImpl(MCI, IVec); |
| } |
| |
| STATISTIC(NumVariantInst, "Number of MCInsts that doesn't have static Desc"); |
| |
| Expected<std::unique_ptr<Instruction>> |
| InstrBuilder::createInstruction(const MCInst &MCI, |
| const SmallVector<SharedInstrument> &IVec) { |
| Expected<const InstrDesc &> DescOrErr = getOrCreateInstrDesc(MCI, IVec); |
| if (!DescOrErr) |
| return DescOrErr.takeError(); |
| const InstrDesc &D = *DescOrErr; |
| Instruction *NewIS = nullptr; |
| std::unique_ptr<Instruction> CreatedIS; |
| bool IsInstRecycled = false; |
| |
| if (!D.IsRecyclable) |
| ++NumVariantInst; |
| |
| if (D.IsRecyclable && InstRecycleCB) { |
| if (auto *I = InstRecycleCB(D)) { |
| NewIS = I; |
| NewIS->reset(); |
| IsInstRecycled = true; |
| } |
| } |
| if (!IsInstRecycled) { |
| CreatedIS = std::make_unique<Instruction>(D, MCI.getOpcode()); |
| NewIS = CreatedIS.get(); |
| } |
| |
| const MCInstrDesc &MCDesc = MCII.get(MCI.getOpcode()); |
| const MCSchedClassDesc &SCDesc = |
| *STI.getSchedModel().getSchedClassDesc(D.SchedClassID); |
| |
| NewIS->setMayLoad(MCDesc.mayLoad()); |
| NewIS->setMayStore(MCDesc.mayStore()); |
| NewIS->setHasSideEffects(MCDesc.hasUnmodeledSideEffects()); |
| NewIS->setBeginGroup(SCDesc.BeginGroup); |
| NewIS->setEndGroup(SCDesc.EndGroup); |
| NewIS->setRetireOOO(SCDesc.RetireOOO); |
| |
| // Check if this is a dependency breaking instruction. |
| APInt Mask; |
| |
| bool IsZeroIdiom = false; |
| bool IsDepBreaking = false; |
| if (MCIA) { |
| unsigned ProcID = STI.getSchedModel().getProcessorID(); |
| IsZeroIdiom = MCIA->isZeroIdiom(MCI, Mask, ProcID); |
| IsDepBreaking = |
| IsZeroIdiom || MCIA->isDependencyBreaking(MCI, Mask, ProcID); |
| if (MCIA->isOptimizableRegisterMove(MCI, ProcID)) |
| NewIS->setOptimizableMove(); |
| } |
| |
| // Initialize Reads first. |
| MCPhysReg RegID = 0; |
| size_t Idx = 0U; |
| for (const ReadDescriptor &RD : D.Reads) { |
| 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. |
| ReadState *RS = nullptr; |
| if (IsInstRecycled && Idx < NewIS->getUses().size()) { |
| NewIS->getUses()[Idx] = ReadState(RD, RegID); |
| RS = &NewIS->getUses()[Idx++]; |
| } else { |
| NewIS->getUses().emplace_back(RD, RegID); |
| RS = &NewIS->getUses().back(); |
| ++Idx; |
| } |
| |
| if (IsDepBreaking) { |
| // A mask of all zeroes means: explicit input operands are not |
| // independent. |
| if (Mask.isZero()) { |
| if (!RD.isImplicitRead()) |
| RS->setIndependentFromDef(); |
| } else { |
| // Check if this register operand is independent according to `Mask`. |
| // Note that Mask may not have enough bits to describe all explicit and |
| // implicit input operands. If this register operand doesn't have a |
| // corresponding bit in Mask, then conservatively assume that it is |
| // dependent. |
| if (Mask.getBitWidth() > RD.UseIndex) { |
| // Okay. This map describe register use `RD.UseIndex`. |
| if (Mask[RD.UseIndex]) |
| RS->setIndependentFromDef(); |
| } |
| } |
| } |
| } |
| if (IsInstRecycled && Idx < NewIS->getUses().size()) |
| NewIS->getUses().pop_back_n(NewIS->getUses().size() - Idx); |
| |
| // Early exit if there are no writes. |
| if (D.Writes.empty()) { |
| if (IsInstRecycled) |
| return llvm::make_error<RecycledInstErr>(NewIS); |
| else |
| return std::move(CreatedIS); |
| } |
| |
| // 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. |
| if (MCIA) |
| MCIA->clearsSuperRegisters(MRI, MCI, WriteMask); |
| |
| // Initialize writes. |
| unsigned WriteIndex = 0; |
| Idx = 0U; |
| for (const WriteDescriptor &WD : D.Writes) { |
| 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!"); |
| if (IsInstRecycled && Idx < NewIS->getDefs().size()) { |
| NewIS->getDefs()[Idx++] = |
| WriteState(WD, RegID, |
| /* ClearsSuperRegs */ WriteMask[WriteIndex], |
| /* WritesZero */ IsZeroIdiom); |
| } else { |
| NewIS->getDefs().emplace_back(WD, RegID, |
| /* ClearsSuperRegs */ WriteMask[WriteIndex], |
| /* WritesZero */ IsZeroIdiom); |
| ++Idx; |
| } |
| ++WriteIndex; |
| } |
| if (IsInstRecycled && Idx < NewIS->getDefs().size()) |
| NewIS->getDefs().pop_back_n(NewIS->getDefs().size() - Idx); |
| |
| if (IsInstRecycled) |
| return llvm::make_error<RecycledInstErr>(NewIS); |
| else |
| return std::move(CreatedIS); |
| } |
| } // namespace mca |
| } // namespace llvm |