| //===--------------------- 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<unsigned>(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<ResourceStrategy> |
| getStrategyFor(const ResourceState &RS) { |
| if (RS.isAResourceGroup() || RS.getNumUnits() > 1) |
| return std::make_unique<DefaultResourceStrategy>(RS.getReadyMask()); |
| return std::unique_ptr<ResourceStrategy>(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<ResourceState>(*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<ResourceStrategy> 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<uint64_t, ResourceUsage> &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; |
| } |
| |
| BusyResourceMask &= ProcResUnitMask; |
| if (BusyResourceMask) |
| return BusyResourceMask; |
| return Desc.UsedProcResGroups & ReservedResourceGroups; |
| } |
| |
| void ResourceManager::issueInstruction( |
| const InstrDesc &Desc, |
| SmallVectorImpl<std::pair<ResourceRef, ResourceCycles>> &Pipes) { |
| for (const std::pair<uint64_t, ResourceUsage> &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<ResourceRef, ResourceCycles>( |
| 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<ResourceRef> &ResourcesFreed) { |
| for (std::pair<ResourceRef, unsigned> &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 |