//===---------------------- ExecuteStage.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 defines the execution stage of an instruction pipeline. /// /// The ExecuteStage is responsible for managing the hardware scheduler /// and issuing notifications that an instruction has been executed. /// //===----------------------------------------------------------------------===// #include "llvm/MCA/Stages/ExecuteStage.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Debug.h" #define DEBUG_TYPE "llvm-mca" namespace llvm { namespace mca { HWStallEvent::GenericEventType toHWStallEventType(Scheduler::Status Status) { switch (Status) { case Scheduler::SC_LOAD_QUEUE_FULL: return HWStallEvent::LoadQueueFull; case Scheduler::SC_STORE_QUEUE_FULL: return HWStallEvent::StoreQueueFull; case Scheduler::SC_BUFFERS_FULL: return HWStallEvent::SchedulerQueueFull; case Scheduler::SC_DISPATCH_GROUP_STALL: return HWStallEvent::DispatchGroupStall; case Scheduler::SC_AVAILABLE: return HWStallEvent::Invalid; } llvm_unreachable("Don't know how to process this StallKind!"); } bool ExecuteStage::isAvailable(const InstRef &IR) const { if (Scheduler::Status S = HWS.isAvailable(IR)) { HWStallEvent::GenericEventType ET = toHWStallEventType(S); notifyEvent(HWStallEvent(ET, IR)); return false; } return true; } Error ExecuteStage::issueInstruction(InstRef &IR) { SmallVector Used; SmallVector Pending; SmallVector Ready; HWS.issueInstruction(IR, Used, Pending, Ready); Instruction &IS = *IR.getInstruction(); NumIssuedOpcodes += IS.getNumMicroOps(); notifyReservedOrReleasedBuffers(IR, /* Reserved */ false); notifyInstructionIssued(IR, Used); if (IS.isExecuted()) { notifyInstructionExecuted(IR); // FIXME: add a buffer of executed instructions. if (Error S = moveToTheNextStage(IR)) return S; } for (const InstRef &I : Pending) notifyInstructionPending(I); for (const InstRef &I : Ready) notifyInstructionReady(I); return ErrorSuccess(); } Error ExecuteStage::issueReadyInstructions() { InstRef IR = HWS.select(); while (IR) { if (Error Err = issueInstruction(IR)) return Err; // Select the next instruction to issue. IR = HWS.select(); } return ErrorSuccess(); } Error ExecuteStage::cycleStart() { SmallVector Freed; SmallVector Executed; SmallVector Pending; SmallVector Ready; HWS.cycleEvent(Freed, Executed, Pending, Ready); NumDispatchedOpcodes = 0; NumIssuedOpcodes = 0; for (const ResourceRef &RR : Freed) notifyResourceAvailable(RR); for (InstRef &IR : Executed) { notifyInstructionExecuted(IR); // FIXME: add a buffer of executed instructions. if (Error S = moveToTheNextStage(IR)) return S; } for (const InstRef &IR : Pending) notifyInstructionPending(IR); for (const InstRef &IR : Ready) notifyInstructionReady(IR); return issueReadyInstructions(); } Error ExecuteStage::cycleEnd() { if (!EnablePressureEvents) return ErrorSuccess(); // Always conservatively report any backpressure events if the dispatch logic // was stalled due to unavailable scheduler resources. if (!HWS.hadTokenStall() && NumDispatchedOpcodes <= NumIssuedOpcodes) return ErrorSuccess(); SmallVector Insts; uint64_t Mask = HWS.analyzeResourcePressure(Insts); if (Mask) { LLVM_DEBUG(dbgs() << "[E] Backpressure increased because of unavailable " "pipeline resources: " << format_hex(Mask, 16) << '\n'); HWPressureEvent Ev(HWPressureEvent::RESOURCES, Insts, Mask); notifyEvent(Ev); } SmallVector RegDeps; SmallVector MemDeps; HWS.analyzeDataDependencies(RegDeps, MemDeps); if (RegDeps.size()) { LLVM_DEBUG( dbgs() << "[E] Backpressure increased by register dependencies\n"); HWPressureEvent Ev(HWPressureEvent::REGISTER_DEPS, RegDeps); notifyEvent(Ev); } if (MemDeps.size()) { LLVM_DEBUG(dbgs() << "[E] Backpressure increased by memory dependencies\n"); HWPressureEvent Ev(HWPressureEvent::MEMORY_DEPS, MemDeps); notifyEvent(Ev); } return ErrorSuccess(); } #ifndef NDEBUG static void verifyInstructionEliminated(const InstRef &IR) { const Instruction &Inst = *IR.getInstruction(); assert(Inst.isEliminated() && "Instruction was not eliminated!"); assert(Inst.isReady() && "Instruction in an inconsistent state!"); // Ensure that instructions eliminated at register renaming stage are in a // consistent state. assert(!Inst.getMayLoad() && !Inst.getMayStore() && "Cannot eliminate a memory op!"); } #endif Error ExecuteStage::handleInstructionEliminated(InstRef &IR) { #ifndef NDEBUG verifyInstructionEliminated(IR); #endif notifyInstructionPending(IR); notifyInstructionReady(IR); notifyInstructionIssued(IR, {}); IR.getInstruction()->forceExecuted(); notifyInstructionExecuted(IR); return moveToTheNextStage(IR); } // Schedule the instruction for execution on the hardware. Error ExecuteStage::execute(InstRef &IR) { assert(isAvailable(IR) && "Scheduler is not available!"); #ifndef NDEBUG // Ensure that the HWS has not stored this instruction in its queues. HWS.instructionCheck(IR); #endif if (IR.getInstruction()->isEliminated()) return handleInstructionEliminated(IR); // Reserve a slot in each buffered resource. Also, mark units with // BufferSize=0 as reserved. Resources with a buffer size of zero will only // be released after MCIS is issued, and all the ReleaseAtCycles for those // units have been consumed. bool IsReadyInstruction = HWS.dispatch(IR); const Instruction &Inst = *IR.getInstruction(); unsigned NumMicroOps = Inst.getNumMicroOps(); NumDispatchedOpcodes += NumMicroOps; notifyReservedOrReleasedBuffers(IR, /* Reserved */ true); if (!IsReadyInstruction) { if (Inst.isPending()) notifyInstructionPending(IR); return ErrorSuccess(); } notifyInstructionPending(IR); // If we did not return early, then the scheduler is ready for execution. notifyInstructionReady(IR); // If we cannot issue immediately, the HWS will add IR to its ready queue for // execution later, so we must return early here. if (!HWS.mustIssueImmediately(IR)) return ErrorSuccess(); // Issue IR to the underlying pipelines. return issueInstruction(IR); } void ExecuteStage::notifyInstructionExecuted(const InstRef &IR) const { LLVM_DEBUG(dbgs() << "[E] Instruction Executed: #" << IR << '\n'); notifyEvent( HWInstructionEvent(HWInstructionEvent::Executed, IR)); } void ExecuteStage::notifyInstructionPending(const InstRef &IR) const { LLVM_DEBUG(dbgs() << "[E] Instruction Pending: #" << IR << '\n'); notifyEvent( HWInstructionEvent(HWInstructionEvent::Pending, IR)); } void ExecuteStage::notifyInstructionReady(const InstRef &IR) const { LLVM_DEBUG(dbgs() << "[E] Instruction Ready: #" << IR << '\n'); notifyEvent( HWInstructionEvent(HWInstructionEvent::Ready, IR)); } void ExecuteStage::notifyResourceAvailable(const ResourceRef &RR) const { LLVM_DEBUG(dbgs() << "[E] Resource Available: [" << RR.first << '.' << RR.second << "]\n"); for (HWEventListener *Listener : getListeners()) Listener->onResourceAvailable(RR); } void ExecuteStage::notifyInstructionIssued( const InstRef &IR, MutableArrayRef Used) const { LLVM_DEBUG({ dbgs() << "[E] Instruction Issued: #" << IR << '\n'; for (const ResourceUse &Use : Used) { assert(Use.second.getDenominator() == 1 && "Invalid cycles!"); dbgs() << "[E] Resource Used: [" << Use.first.first << '.' << Use.first.second << "], "; dbgs() << "cycles: " << Use.second.getNumerator() << '\n'; } }); // Replace resource masks with valid resource processor IDs. for (ResourceUse &Use : Used) Use.first.first = HWS.getResourceID(Use.first.first); notifyEvent(HWInstructionIssuedEvent(IR, Used)); } void ExecuteStage::notifyReservedOrReleasedBuffers(const InstRef &IR, bool Reserved) const { uint64_t UsedBuffers = IR.getInstruction()->getDesc().UsedBuffers; if (!UsedBuffers) return; SmallVector BufferIDs(llvm::popcount(UsedBuffers), 0); for (unsigned I = 0, E = BufferIDs.size(); I < E; ++I) { uint64_t CurrentBufferMask = UsedBuffers & (-UsedBuffers); BufferIDs[I] = HWS.getResourceID(CurrentBufferMask); UsedBuffers ^= CurrentBufferMask; } if (Reserved) { for (HWEventListener *Listener : getListeners()) Listener->onReservedBuffers(IR, BufferIDs); return; } for (HWEventListener *Listener : getListeners()) Listener->onReleasedBuffers(IR, BufferIDs); } } // namespace mca } // namespace llvm