1 //===--------------------- ResourceManager.cpp ------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 /// \file 9 /// 10 /// The classes here represent processor resource units and their management 11 /// strategy. These classes are managed by the Scheduler. 12 /// 13 //===----------------------------------------------------------------------===// 14 15 #include "llvm/MCA/HardwareUnits/ResourceManager.h" 16 #include "llvm/MCA/Support.h" 17 #include "llvm/Support/Debug.h" 18 #include "llvm/Support/raw_ostream.h" 19 20 namespace llvm { 21 namespace mca { 22 23 #define DEBUG_TYPE "llvm-mca" 24 ResourceStrategy::~ResourceStrategy() = default; 25 26 static uint64_t selectImpl(uint64_t CandidateMask, 27 uint64_t &NextInSequenceMask) { 28 // The upper bit set in CandidateMask identifies our next candidate resource. 29 CandidateMask = 1ULL << getResourceStateIndex(CandidateMask); 30 NextInSequenceMask &= (CandidateMask | (CandidateMask - 1)); 31 return CandidateMask; 32 } 33 34 uint64_t DefaultResourceStrategy::select(uint64_t ReadyMask) { 35 // This method assumes that ReadyMask cannot be zero. 36 uint64_t CandidateMask = ReadyMask & NextInSequenceMask; 37 if (CandidateMask) 38 return selectImpl(CandidateMask, NextInSequenceMask); 39 40 NextInSequenceMask = ResourceUnitMask ^ RemovedFromNextInSequence; 41 RemovedFromNextInSequence = 0; 42 CandidateMask = ReadyMask & NextInSequenceMask; 43 if (CandidateMask) 44 return selectImpl(CandidateMask, NextInSequenceMask); 45 46 NextInSequenceMask = ResourceUnitMask; 47 CandidateMask = ReadyMask & NextInSequenceMask; 48 return selectImpl(CandidateMask, NextInSequenceMask); 49 } 50 51 void DefaultResourceStrategy::used(uint64_t Mask) { 52 if (Mask > NextInSequenceMask) { 53 RemovedFromNextInSequence |= Mask; 54 return; 55 } 56 57 NextInSequenceMask &= (~Mask); 58 if (NextInSequenceMask) 59 return; 60 61 NextInSequenceMask = ResourceUnitMask ^ RemovedFromNextInSequence; 62 RemovedFromNextInSequence = 0; 63 } 64 65 ResourceState::ResourceState(const MCProcResourceDesc &Desc, unsigned Index, 66 uint64_t Mask) 67 : ProcResourceDescIndex(Index), ResourceMask(Mask), 68 BufferSize(Desc.BufferSize), IsAGroup(llvm::popcount(ResourceMask) > 1) { 69 if (IsAGroup) { 70 ResourceSizeMask = 71 ResourceMask ^ 1ULL << getResourceStateIndex(ResourceMask); 72 } else { 73 ResourceSizeMask = (1ULL << Desc.NumUnits) - 1; 74 } 75 ReadyMask = ResourceSizeMask; 76 AvailableSlots = BufferSize == -1 ? 0U : static_cast<unsigned>(BufferSize); 77 Unavailable = false; 78 } 79 80 bool ResourceState::isReady(unsigned NumUnits) const { 81 return (!isReserved() || isADispatchHazard()) && 82 (unsigned)llvm::popcount(ReadyMask) >= NumUnits; 83 } 84 85 ResourceStateEvent ResourceState::isBufferAvailable() const { 86 if (isADispatchHazard() && isReserved()) 87 return RS_RESERVED; 88 if (!isBuffered() || AvailableSlots) 89 return RS_BUFFER_AVAILABLE; 90 return RS_BUFFER_UNAVAILABLE; 91 } 92 93 #ifndef NDEBUG 94 void ResourceState::dump() const { 95 dbgs() << "MASK=" << format_hex(ResourceMask, 16) 96 << ", SZMASK=" << format_hex(ResourceSizeMask, 16) 97 << ", RDYMASK=" << format_hex(ReadyMask, 16) 98 << ", BufferSize=" << BufferSize 99 << ", AvailableSlots=" << AvailableSlots 100 << ", Reserved=" << Unavailable << '\n'; 101 } 102 #endif 103 104 static std::unique_ptr<ResourceStrategy> 105 getStrategyFor(const ResourceState &RS) { 106 if (RS.isAResourceGroup() || RS.getNumUnits() > 1) 107 return std::make_unique<DefaultResourceStrategy>(RS.getReadyMask()); 108 return std::unique_ptr<ResourceStrategy>(nullptr); 109 } 110 111 ResourceManager::ResourceManager(const MCSchedModel &SM) 112 : Resources(SM.getNumProcResourceKinds() - 1), 113 Strategies(SM.getNumProcResourceKinds() - 1), 114 Resource2Groups(SM.getNumProcResourceKinds() - 1, 0), 115 ProcResID2Mask(SM.getNumProcResourceKinds(), 0), 116 ResIndex2ProcResID(SM.getNumProcResourceKinds() - 1, 0), 117 ProcResUnitMask(0), ReservedResourceGroups(0), AvailableBuffers(~0ULL), 118 ReservedBuffers(0) { 119 computeProcResourceMasks(SM, ProcResID2Mask); 120 121 // initialize vector ResIndex2ProcResID. 122 for (unsigned I = 1, E = SM.getNumProcResourceKinds(); I < E; ++I) { 123 unsigned Index = getResourceStateIndex(ProcResID2Mask[I]); 124 ResIndex2ProcResID[Index] = I; 125 } 126 127 for (unsigned I = 1, E = SM.getNumProcResourceKinds(); I < E; ++I) { 128 uint64_t Mask = ProcResID2Mask[I]; 129 unsigned Index = getResourceStateIndex(Mask); 130 Resources[Index] = 131 std::make_unique<ResourceState>(*SM.getProcResource(I), I, Mask); 132 Strategies[Index] = getStrategyFor(*Resources[Index]); 133 } 134 135 for (unsigned I = 1, E = SM.getNumProcResourceKinds(); I < E; ++I) { 136 uint64_t Mask = ProcResID2Mask[I]; 137 unsigned Index = getResourceStateIndex(Mask); 138 const ResourceState &RS = *Resources[Index]; 139 if (!RS.isAResourceGroup()) { 140 ProcResUnitMask |= Mask; 141 continue; 142 } 143 144 uint64_t GroupMaskIdx = 1ULL << Index; 145 Mask -= GroupMaskIdx; 146 while (Mask) { 147 // Extract lowest set isolated bit. 148 uint64_t Unit = Mask & (-Mask); 149 unsigned IndexUnit = getResourceStateIndex(Unit); 150 Resource2Groups[IndexUnit] |= GroupMaskIdx; 151 Mask ^= Unit; 152 } 153 } 154 155 AvailableProcResUnits = ProcResUnitMask; 156 } 157 158 void ResourceManager::setCustomStrategyImpl(std::unique_ptr<ResourceStrategy> S, 159 uint64_t ResourceMask) { 160 unsigned Index = getResourceStateIndex(ResourceMask); 161 assert(Index < Resources.size() && "Invalid processor resource index!"); 162 assert(S && "Unexpected null strategy in input!"); 163 Strategies[Index] = std::move(S); 164 } 165 166 unsigned ResourceManager::resolveResourceMask(uint64_t Mask) const { 167 return ResIndex2ProcResID[getResourceStateIndex(Mask)]; 168 } 169 170 unsigned ResourceManager::getNumUnits(uint64_t ResourceID) const { 171 return Resources[getResourceStateIndex(ResourceID)]->getNumUnits(); 172 } 173 174 // Returns the actual resource consumed by this Use. 175 // First, is the primary resource ID. 176 // Second, is the specific sub-resource ID. 177 ResourceRef ResourceManager::selectPipe(uint64_t ResourceID) { 178 unsigned Index = getResourceStateIndex(ResourceID); 179 assert(Index < Resources.size() && "Invalid resource use!"); 180 ResourceState &RS = *Resources[Index]; 181 assert(RS.isReady() && "No available units to select!"); 182 183 // Special case where RS is not a group, and it only declares a single 184 // resource unit. 185 if (!RS.isAResourceGroup() && RS.getNumUnits() == 1) 186 return std::make_pair(ResourceID, RS.getReadyMask()); 187 188 uint64_t SubResourceID = Strategies[Index]->select(RS.getReadyMask()); 189 if (RS.isAResourceGroup()) 190 return selectPipe(SubResourceID); 191 return std::make_pair(ResourceID, SubResourceID); 192 } 193 194 void ResourceManager::use(const ResourceRef &RR) { 195 // Mark the sub-resource referenced by RR as used. 196 unsigned RSID = getResourceStateIndex(RR.first); 197 ResourceState &RS = *Resources[RSID]; 198 RS.markSubResourceAsUsed(RR.second); 199 // Remember to update the resource strategy for non-group resources with 200 // multiple units. 201 if (RS.getNumUnits() > 1) 202 Strategies[RSID]->used(RR.second); 203 204 // If there are still available units in RR.first, 205 // then we are done. 206 if (RS.isReady()) 207 return; 208 209 AvailableProcResUnits ^= RR.first; 210 211 // Notify groups that RR.first is no longer available. 212 uint64_t Users = Resource2Groups[RSID]; 213 while (Users) { 214 // Extract lowest set isolated bit. 215 unsigned GroupIndex = getResourceStateIndex(Users & (-Users)); 216 ResourceState &CurrentUser = *Resources[GroupIndex]; 217 CurrentUser.markSubResourceAsUsed(RR.first); 218 Strategies[GroupIndex]->used(RR.first); 219 // Reset lowest set bit. 220 Users &= Users - 1; 221 } 222 } 223 224 void ResourceManager::release(const ResourceRef &RR) { 225 unsigned RSID = getResourceStateIndex(RR.first); 226 ResourceState &RS = *Resources[RSID]; 227 bool WasFullyUsed = !RS.isReady(); 228 RS.releaseSubResource(RR.second); 229 if (!WasFullyUsed) 230 return; 231 232 AvailableProcResUnits ^= RR.first; 233 234 // Notify groups that RR.first is now available again. 235 uint64_t Users = Resource2Groups[RSID]; 236 while (Users) { 237 unsigned GroupIndex = getResourceStateIndex(Users & (-Users)); 238 ResourceState &CurrentUser = *Resources[GroupIndex]; 239 CurrentUser.releaseSubResource(RR.first); 240 Users &= Users - 1; 241 } 242 } 243 244 ResourceStateEvent 245 ResourceManager::canBeDispatched(uint64_t ConsumedBuffers) const { 246 if (ConsumedBuffers & ReservedBuffers) 247 return ResourceStateEvent::RS_RESERVED; 248 if (ConsumedBuffers & (~AvailableBuffers)) 249 return ResourceStateEvent::RS_BUFFER_UNAVAILABLE; 250 return ResourceStateEvent::RS_BUFFER_AVAILABLE; 251 } 252 253 void ResourceManager::reserveBuffers(uint64_t ConsumedBuffers) { 254 while (ConsumedBuffers) { 255 uint64_t CurrentBuffer = ConsumedBuffers & (-ConsumedBuffers); 256 ResourceState &RS = *Resources[getResourceStateIndex(CurrentBuffer)]; 257 ConsumedBuffers ^= CurrentBuffer; 258 assert(RS.isBufferAvailable() == ResourceStateEvent::RS_BUFFER_AVAILABLE); 259 if (!RS.reserveBuffer()) 260 AvailableBuffers ^= CurrentBuffer; 261 if (RS.isADispatchHazard()) { 262 // Reserve this buffer now, and release it once pipeline resources 263 // consumed by the instruction become available again. 264 // We do this to simulate an in-order dispatch/issue of instructions. 265 ReservedBuffers ^= CurrentBuffer; 266 } 267 } 268 } 269 270 void ResourceManager::releaseBuffers(uint64_t ConsumedBuffers) { 271 AvailableBuffers |= ConsumedBuffers; 272 while (ConsumedBuffers) { 273 uint64_t CurrentBuffer = ConsumedBuffers & (-ConsumedBuffers); 274 ResourceState &RS = *Resources[getResourceStateIndex(CurrentBuffer)]; 275 ConsumedBuffers ^= CurrentBuffer; 276 RS.releaseBuffer(); 277 // Do not unreserve dispatch hazard resource buffers. Wait until all 278 // pipeline resources have been freed too. 279 } 280 } 281 282 uint64_t ResourceManager::checkAvailability(const InstrDesc &Desc) const { 283 uint64_t BusyResourceMask = 0; 284 uint64_t ConsumedResourceMask = 0; 285 DenseMap<uint64_t, unsigned> AvailableUnits; 286 287 for (const std::pair<uint64_t, ResourceUsage> &E : Desc.Resources) { 288 unsigned NumUnits = E.second.isReserved() ? 0U : E.second.NumUnits; 289 const ResourceState &RS = *Resources[getResourceStateIndex(E.first)]; 290 if (!RS.isReady(NumUnits)) { 291 BusyResourceMask |= E.first; 292 continue; 293 } 294 295 if (Desc.HasPartiallyOverlappingGroups && !RS.isAResourceGroup()) { 296 unsigned NumAvailableUnits = llvm::popcount(RS.getReadyMask()); 297 NumAvailableUnits -= NumUnits; 298 AvailableUnits[E.first] = NumAvailableUnits; 299 if (!NumAvailableUnits) 300 ConsumedResourceMask |= E.first; 301 } 302 } 303 304 BusyResourceMask &= ProcResUnitMask; 305 if (BusyResourceMask) 306 return BusyResourceMask; 307 308 BusyResourceMask = Desc.UsedProcResGroups & ReservedResourceGroups; 309 if (!Desc.HasPartiallyOverlappingGroups || BusyResourceMask) 310 return BusyResourceMask; 311 312 // If this instruction has overlapping groups, make sure that we can 313 // select at least one unit per group. 314 for (const std::pair<uint64_t, ResourceUsage> &E : Desc.Resources) { 315 const ResourceState &RS = *Resources[getResourceStateIndex(E.first)]; 316 if (!E.second.isReserved() && RS.isAResourceGroup()) { 317 uint64_t ReadyMask = RS.getReadyMask() & ~ConsumedResourceMask; 318 if (!ReadyMask) { 319 BusyResourceMask |= RS.getReadyMask(); 320 continue; 321 } 322 323 uint64_t ResourceMask = llvm::bit_floor(ReadyMask); 324 325 auto [it, Inserted] = AvailableUnits.try_emplace(ResourceMask); 326 if (Inserted) { 327 unsigned Index = getResourceStateIndex(ResourceMask); 328 unsigned NumUnits = llvm::popcount(Resources[Index]->getReadyMask()); 329 it->second = NumUnits; 330 } 331 332 if (!it->second) { 333 BusyResourceMask |= it->first; 334 continue; 335 } 336 337 it->second--; 338 if (!it->second) 339 ConsumedResourceMask |= it->first; 340 } 341 } 342 343 return BusyResourceMask; 344 } 345 346 void ResourceManager::issueInstructionImpl( 347 const InstrDesc &Desc, SmallVectorImpl<ResourceWithCycles> &Pipes) { 348 349 // Step 1. 350 // - Issue writes to non-group resources. 351 // - Issue writes to groups with only a single resource unit available. 352 // - Update reserved groups (if any) 353 // - Add any remaining resource usage requests to a Worklist. 354 SmallVector<std::pair<uint64_t, ResourceUsage>, 4> Worklist; 355 356 using ResourceWithUsage = std::pair<uint64_t, ResourceUsage>; 357 358 for (const ResourceWithUsage &R : Desc.Resources) { 359 const CycleSegment &CS = R.second.CS; 360 if (!CS.size()) { 361 releaseResource(R.first); 362 continue; 363 } 364 365 assert(CS.begin() == 0 && "Invalid {Start, End} cycles!"); 366 if (R.second.isReserved()) { 367 assert((llvm::popcount(R.first) > 1) && "Expected a group!"); 368 // Mark this group as reserved. 369 assert(R.second.isReserved()); 370 reserveResource(R.first); 371 BusyResources[ResourceRef(R.first, R.first)] += CS.size(); 372 continue; 373 } 374 375 const ResourceState &RS = *Resources[getResourceStateIndex(R.first)]; 376 if (RS.isAResourceGroup() && RS.getNumReadyUnits() > 1) { 377 Worklist.push_back(R); 378 continue; 379 } 380 381 ResourceRef Pipe = selectPipe(R.first); 382 use(Pipe); 383 BusyResources[Pipe] += CS.size(); 384 Pipes.emplace_back(std::make_pair(Pipe, ReleaseAtCycles(CS.size()))); 385 } 386 387 // Step 2. 388 // Prioritize writes to groups with less available resources. 389 // NOTE: this algorithm has quadratic complexity in the worst case scenario. 390 // On average, this algorithm is expected to perform quite well and always 391 // converge in very few iterations. That is mainly because instructions rarely 392 // consume more than two or three resource groups. 393 394 while (!Worklist.empty()) { 395 sort(Worklist, [&](const ResourceWithUsage &Lhs, 396 const ResourceWithUsage &Rhs) { 397 const ResourceState &LhsRS = *Resources[getResourceStateIndex(Lhs.first)]; 398 const ResourceState &RhsRS = *Resources[getResourceStateIndex(Rhs.first)]; 399 uint64_t LhsReadyUnits = LhsRS.getNumReadyUnits(); 400 uint64_t RhsReadyUnits = RhsRS.getNumReadyUnits(); 401 if (LhsReadyUnits == RhsReadyUnits) 402 return Lhs.first < Rhs.first; 403 return LhsReadyUnits < RhsReadyUnits; 404 }); 405 406 SmallVector<ResourceWithUsage, 4> NewWorklist; 407 408 for (unsigned I = 0, E = Worklist.size(); I < E; ++I) { 409 const auto &Elt = Worklist[I]; 410 const ResourceState &RS = *Resources[getResourceStateIndex(Elt.first)]; 411 412 if (I == 0 || RS.getNumReadyUnits() == 1) { 413 ResourceRef Pipe = selectPipe(Elt.first); 414 use(Pipe); 415 const CycleSegment &CS = Elt.second.CS; 416 BusyResources[Pipe] += CS.size(); 417 Pipes.emplace_back(std::make_pair(Pipe, ReleaseAtCycles(CS.size()))); 418 continue; 419 } 420 421 NewWorklist.push_back(Elt); 422 } 423 424 swap(NewWorklist, Worklist); 425 }; 426 } 427 428 void ResourceManager::fastIssueInstruction( 429 const InstrDesc &Desc, SmallVectorImpl<ResourceWithCycles> &Pipes) { 430 for (const std::pair<uint64_t, ResourceUsage> &R : Desc.Resources) { 431 const CycleSegment &CS = R.second.CS; 432 if (!CS.size()) { 433 releaseResource(R.first); 434 continue; 435 } 436 437 assert(CS.begin() == 0 && "Invalid {Start, End} cycles!"); 438 if (!R.second.isReserved()) { 439 ResourceRef Pipe = selectPipe(R.first); 440 use(Pipe); 441 BusyResources[Pipe] += CS.size(); 442 Pipes.emplace_back(std::pair<ResourceRef, ReleaseAtCycles>( 443 Pipe, ReleaseAtCycles(CS.size()))); 444 } else { 445 assert((llvm::popcount(R.first) > 1) && "Expected a group!"); 446 // Mark this group as reserved. 447 assert(R.second.isReserved()); 448 reserveResource(R.first); 449 BusyResources[ResourceRef(R.first, R.first)] += CS.size(); 450 } 451 } 452 } 453 454 void ResourceManager::cycleEvent(SmallVectorImpl<ResourceRef> &ResourcesFreed) { 455 for (std::pair<ResourceRef, unsigned> &BR : BusyResources) { 456 if (BR.second) 457 BR.second--; 458 if (!BR.second) { 459 // Release this resource. 460 const ResourceRef &RR = BR.first; 461 462 if (llvm::popcount(RR.first) == 1) 463 release(RR); 464 releaseResource(RR.first); 465 ResourcesFreed.push_back(RR); 466 } 467 } 468 469 for (const ResourceRef &RF : ResourcesFreed) 470 BusyResources.erase(RF); 471 } 472 473 void ResourceManager::reserveResource(uint64_t ResourceID) { 474 const unsigned Index = getResourceStateIndex(ResourceID); 475 ResourceState &Resource = *Resources[Index]; 476 assert(Resource.isAResourceGroup() && !Resource.isReserved() && 477 "Unexpected resource state found!"); 478 Resource.setReserved(); 479 ReservedResourceGroups ^= 1ULL << Index; 480 } 481 482 void ResourceManager::releaseResource(uint64_t ResourceID) { 483 const unsigned Index = getResourceStateIndex(ResourceID); 484 ResourceState &Resource = *Resources[Index]; 485 Resource.clearReserved(); 486 if (Resource.isAResourceGroup()) 487 ReservedResourceGroups ^= 1ULL << Index; 488 // Now it is safe to release dispatch/issue resources. 489 if (Resource.isADispatchHazard()) 490 ReservedBuffers ^= 1ULL << Index; 491 } 492 493 } // namespace mca 494 } // namespace llvm 495