//===--------------------- ResourceManager.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 /// /// The classes here represent processor resource units and their management /// strategy. These classes are managed by the Scheduler. /// //===----------------------------------------------------------------------===// #include "llvm/MCA/HardwareUnits/ResourceManager.h" #include "llvm/MCA/Support.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" namespace llvm { namespace mca { #define DEBUG_TYPE "llvm-mca" ResourceStrategy::~ResourceStrategy() = default; static uint64_t selectImpl(uint64_t CandidateMask, uint64_t &NextInSequenceMask) { // The upper bit set in CandidateMask identifies our next candidate resource. CandidateMask = 1ULL << getResourceStateIndex(CandidateMask); NextInSequenceMask &= (CandidateMask | (CandidateMask - 1)); return CandidateMask; } uint64_t DefaultResourceStrategy::select(uint64_t ReadyMask) { // This method assumes that ReadyMask cannot be zero. uint64_t CandidateMask = ReadyMask & NextInSequenceMask; if (CandidateMask) return selectImpl(CandidateMask, NextInSequenceMask); NextInSequenceMask = ResourceUnitMask ^ RemovedFromNextInSequence; RemovedFromNextInSequence = 0; CandidateMask = ReadyMask & NextInSequenceMask; if (CandidateMask) return selectImpl(CandidateMask, NextInSequenceMask); NextInSequenceMask = ResourceUnitMask; CandidateMask = ReadyMask & NextInSequenceMask; return selectImpl(CandidateMask, NextInSequenceMask); } void DefaultResourceStrategy::used(uint64_t Mask) { if (Mask > NextInSequenceMask) { RemovedFromNextInSequence |= Mask; return; } NextInSequenceMask &= (~Mask); if (NextInSequenceMask) return; NextInSequenceMask = ResourceUnitMask ^ RemovedFromNextInSequence; RemovedFromNextInSequence = 0; } ResourceState::ResourceState(const MCProcResourceDesc &Desc, unsigned Index, uint64_t Mask) : ProcResourceDescIndex(Index), ResourceMask(Mask), BufferSize(Desc.BufferSize), IsAGroup(countPopulation(ResourceMask) > 1) { if (IsAGroup) { ResourceSizeMask = ResourceMask ^ 1ULL << getResourceStateIndex(ResourceMask); } else { ResourceSizeMask = (1ULL << Desc.NumUnits) - 1; } ReadyMask = ResourceSizeMask; AvailableSlots = BufferSize == -1 ? 0U : static_cast(BufferSize); Unavailable = false; } bool ResourceState::isReady(unsigned NumUnits) const { return (!isReserved() || isADispatchHazard()) && countPopulation(ReadyMask) >= NumUnits; } ResourceStateEvent ResourceState::isBufferAvailable() const { if (isADispatchHazard() && isReserved()) return RS_RESERVED; if (!isBuffered() || AvailableSlots) return RS_BUFFER_AVAILABLE; return RS_BUFFER_UNAVAILABLE; } #ifndef NDEBUG void ResourceState::dump() const { dbgs() << "MASK=" << format_hex(ResourceMask, 16) << ", SZMASK=" << format_hex(ResourceSizeMask, 16) << ", RDYMASK=" << format_hex(ReadyMask, 16) << ", BufferSize=" << BufferSize << ", AvailableSlots=" << AvailableSlots << ", Reserved=" << Unavailable << '\n'; } #endif static std::unique_ptr getStrategyFor(const ResourceState &RS) { if (RS.isAResourceGroup() || RS.getNumUnits() > 1) return std::make_unique(RS.getReadyMask()); return std::unique_ptr(nullptr); } ResourceManager::ResourceManager(const MCSchedModel &SM) : Resources(SM.getNumProcResourceKinds() - 1), Strategies(SM.getNumProcResourceKinds() - 1), Resource2Groups(SM.getNumProcResourceKinds() - 1, 0), ProcResID2Mask(SM.getNumProcResourceKinds(), 0), ResIndex2ProcResID(SM.getNumProcResourceKinds() - 1, 0), ProcResUnitMask(0), ReservedResourceGroups(0), AvailableBuffers(~0ULL), ReservedBuffers(0) { computeProcResourceMasks(SM, ProcResID2Mask); // initialize vector ResIndex2ProcResID. for (unsigned I = 1, E = SM.getNumProcResourceKinds(); I < E; ++I) { unsigned Index = getResourceStateIndex(ProcResID2Mask[I]); ResIndex2ProcResID[Index] = I; } for (unsigned I = 1, E = SM.getNumProcResourceKinds(); I < E; ++I) { uint64_t Mask = ProcResID2Mask[I]; unsigned Index = getResourceStateIndex(Mask); Resources[Index] = std::make_unique(*SM.getProcResource(I), I, Mask); Strategies[Index] = getStrategyFor(*Resources[Index]); } for (unsigned I = 1, E = SM.getNumProcResourceKinds(); I < E; ++I) { uint64_t Mask = ProcResID2Mask[I]; unsigned Index = getResourceStateIndex(Mask); const ResourceState &RS = *Resources[Index]; if (!RS.isAResourceGroup()) { ProcResUnitMask |= Mask; continue; } uint64_t GroupMaskIdx = 1ULL << Index; Mask -= GroupMaskIdx; while (Mask) { // Extract lowest set isolated bit. uint64_t Unit = Mask & (-Mask); unsigned IndexUnit = getResourceStateIndex(Unit); Resource2Groups[IndexUnit] |= GroupMaskIdx; Mask ^= Unit; } } AvailableProcResUnits = ProcResUnitMask; } void ResourceManager::setCustomStrategyImpl(std::unique_ptr S, uint64_t ResourceMask) { unsigned Index = getResourceStateIndex(ResourceMask); assert(Index < Resources.size() && "Invalid processor resource index!"); assert(S && "Unexpected null strategy in input!"); Strategies[Index] = std::move(S); } unsigned ResourceManager::resolveResourceMask(uint64_t Mask) const { return ResIndex2ProcResID[getResourceStateIndex(Mask)]; } unsigned ResourceManager::getNumUnits(uint64_t ResourceID) const { return Resources[getResourceStateIndex(ResourceID)]->getNumUnits(); } // Returns the actual resource consumed by this Use. // First, is the primary resource ID. // Second, is the specific sub-resource ID. ResourceRef ResourceManager::selectPipe(uint64_t ResourceID) { unsigned Index = getResourceStateIndex(ResourceID); assert(Index < Resources.size() && "Invalid resource use!"); ResourceState &RS = *Resources[Index]; assert(RS.isReady() && "No available units to select!"); // Special case where RS is not a group, and it only declares a single // resource unit. if (!RS.isAResourceGroup() && RS.getNumUnits() == 1) return std::make_pair(ResourceID, RS.getReadyMask()); uint64_t SubResourceID = Strategies[Index]->select(RS.getReadyMask()); if (RS.isAResourceGroup()) return selectPipe(SubResourceID); return std::make_pair(ResourceID, SubResourceID); } void ResourceManager::use(const ResourceRef &RR) { // Mark the sub-resource referenced by RR as used. unsigned RSID = getResourceStateIndex(RR.first); ResourceState &RS = *Resources[RSID]; RS.markSubResourceAsUsed(RR.second); // Remember to update the resource strategy for non-group resources with // multiple units. if (RS.getNumUnits() > 1) Strategies[RSID]->used(RR.second); // If there are still available units in RR.first, // then we are done. if (RS.isReady()) return; AvailableProcResUnits ^= RR.first; // Notify groups that RR.first is no longer available. uint64_t Users = Resource2Groups[RSID]; while (Users) { // Extract lowest set isolated bit. unsigned GroupIndex = getResourceStateIndex(Users & (-Users)); ResourceState &CurrentUser = *Resources[GroupIndex]; CurrentUser.markSubResourceAsUsed(RR.first); Strategies[GroupIndex]->used(RR.first); // Reset lowest set bit. Users &= Users - 1; } } void ResourceManager::release(const ResourceRef &RR) { unsigned RSID = getResourceStateIndex(RR.first); ResourceState &RS = *Resources[RSID]; bool WasFullyUsed = !RS.isReady(); RS.releaseSubResource(RR.second); if (!WasFullyUsed) return; AvailableProcResUnits ^= RR.first; // Notify groups that RR.first is now available again. uint64_t Users = Resource2Groups[RSID]; while (Users) { unsigned GroupIndex = getResourceStateIndex(Users & (-Users)); ResourceState &CurrentUser = *Resources[GroupIndex]; CurrentUser.releaseSubResource(RR.first); Users &= Users - 1; } } ResourceStateEvent ResourceManager::canBeDispatched(uint64_t ConsumedBuffers) const { if (ConsumedBuffers & ReservedBuffers) return ResourceStateEvent::RS_RESERVED; if (ConsumedBuffers & (~AvailableBuffers)) return ResourceStateEvent::RS_BUFFER_UNAVAILABLE; return ResourceStateEvent::RS_BUFFER_AVAILABLE; } void ResourceManager::reserveBuffers(uint64_t ConsumedBuffers) { while (ConsumedBuffers) { uint64_t CurrentBuffer = ConsumedBuffers & (-ConsumedBuffers); ResourceState &RS = *Resources[getResourceStateIndex(CurrentBuffer)]; ConsumedBuffers ^= CurrentBuffer; assert(RS.isBufferAvailable() == ResourceStateEvent::RS_BUFFER_AVAILABLE); if (!RS.reserveBuffer()) AvailableBuffers ^= CurrentBuffer; if (RS.isADispatchHazard()) { // Reserve this buffer now, and release it once pipeline resources // consumed by the instruction become available again. // We do this to simulate an in-order dispatch/issue of instructions. ReservedBuffers ^= CurrentBuffer; } } } void ResourceManager::releaseBuffers(uint64_t ConsumedBuffers) { AvailableBuffers |= ConsumedBuffers; while (ConsumedBuffers) { uint64_t CurrentBuffer = ConsumedBuffers & (-ConsumedBuffers); ResourceState &RS = *Resources[getResourceStateIndex(CurrentBuffer)]; ConsumedBuffers ^= CurrentBuffer; RS.releaseBuffer(); // Do not unreserve dispatch hazard resource buffers. Wait until all // pipeline resources have been freed too. } } uint64_t ResourceManager::checkAvailability(const InstrDesc &Desc) const { uint64_t BusyResourceMask = 0; for (const std::pair &E : Desc.Resources) { unsigned NumUnits = E.second.isReserved() ? 0U : E.second.NumUnits; unsigned Index = getResourceStateIndex(E.first); if (!Resources[Index]->isReady(NumUnits)) BusyResourceMask |= E.first; } uint64_t ImplicitUses = Desc.ImplicitlyUsedProcResUnits; while (ImplicitUses) { uint64_t Use = ImplicitUses & -ImplicitUses; ImplicitUses ^= Use; unsigned Index = getResourceStateIndex(Use); if (!Resources[Index]->isReady(/* NumUnits */ 1)) BusyResourceMask |= Index; } BusyResourceMask &= ProcResUnitMask; if (BusyResourceMask) return BusyResourceMask; return Desc.UsedProcResGroups & ReservedResourceGroups; } void ResourceManager::issueInstruction( const InstrDesc &Desc, SmallVectorImpl> &Pipes) { for (const std::pair &R : Desc.Resources) { const CycleSegment &CS = R.second.CS; if (!CS.size()) { releaseResource(R.first); continue; } assert(CS.begin() == 0 && "Invalid {Start, End} cycles!"); if (!R.second.isReserved()) { ResourceRef Pipe = selectPipe(R.first); use(Pipe); BusyResources[Pipe] += CS.size(); Pipes.emplace_back(std::pair( Pipe, ResourceCycles(CS.size()))); } else { assert((countPopulation(R.first) > 1) && "Expected a group!"); // Mark this group as reserved. assert(R.second.isReserved()); reserveResource(R.first); BusyResources[ResourceRef(R.first, R.first)] += CS.size(); } } } void ResourceManager::cycleEvent(SmallVectorImpl &ResourcesFreed) { for (std::pair &BR : BusyResources) { if (BR.second) BR.second--; if (!BR.second) { // Release this resource. const ResourceRef &RR = BR.first; if (countPopulation(RR.first) == 1) release(RR); releaseResource(RR.first); ResourcesFreed.push_back(RR); } } for (const ResourceRef &RF : ResourcesFreed) BusyResources.erase(RF); } void ResourceManager::reserveResource(uint64_t ResourceID) { const unsigned Index = getResourceStateIndex(ResourceID); ResourceState &Resource = *Resources[Index]; assert(Resource.isAResourceGroup() && !Resource.isReserved() && "Unexpected resource state found!"); Resource.setReserved(); ReservedResourceGroups ^= 1ULL << Index; } void ResourceManager::releaseResource(uint64_t ResourceID) { const unsigned Index = getResourceStateIndex(ResourceID); ResourceState &Resource = *Resources[Index]; Resource.clearReserved(); if (Resource.isAResourceGroup()) ReservedResourceGroups ^= 1ULL << Index; // Now it is safe to release dispatch/issue resources. if (Resource.isADispatchHazard()) ReservedBuffers ^= 1ULL << Index; } } // namespace mca } // namespace llvm