//===- PassTiming.cpp -----------------------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// #include "PassDetail.h" #include "mlir/Pass/PassManager.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Threading.h" #include #include using namespace mlir; using namespace mlir::detail; //===----------------------------------------------------------------------===// // PassTiming //===----------------------------------------------------------------------===// namespace { struct PassTiming : public PassInstrumentation { PassTiming(TimingScope &timingScope) : rootScope(timingScope) {} PassTiming(std::unique_ptr tm) : ownedTimingManager(std::move(tm)), ownedTimingScope(ownedTimingManager->getRootScope()), rootScope(ownedTimingScope) {} /// If a pass can spawn additional work on other threads, it records the /// index to its currently active timer here. Passes that run on a /// newly-forked thread will check this list to find the active timer of the /// parent thread into which the new thread should be nested. DenseMap parentTimerIndices; /// A stack of the currently active timing scopes per thread. DenseMap> activeThreadTimers; /// The timing manager owned by this instrumentation (in case timing was /// enabled by the user on the pass manager without providing an external /// timing manager). This *must* appear before the `ownedTimingScope` to /// ensure the timing manager is destroyed *after* the scope, since the latter /// may hold a timer that points into the former. std::unique_ptr ownedTimingManager; TimingScope ownedTimingScope; /// The root timing scope into which timing is reported. TimingScope &rootScope; //===--------------------------------------------------------------------===// // Pipeline //===--------------------------------------------------------------------===// void runBeforePipeline(std::optional name, const PipelineParentInfo &parentInfo) override { auto tid = llvm::get_threadid(); auto &activeTimers = activeThreadTimers[tid]; // Find the parent scope, either using the parent info or the root scope // (e.g. in the case of the top-level pipeline). TimingScope *parentScope; auto it = parentTimerIndices.find(parentInfo); if (it != parentTimerIndices.end()) parentScope = &activeThreadTimers[parentInfo.parentThreadID][it->second]; else parentScope = &rootScope; // Use nullptr to anchor op-agnostic pipelines, otherwise use the name of // the operation. const void *timerId = name ? name->getAsOpaquePointer() : nullptr; activeTimers.push_back(parentScope->nest(timerId, [name] { return ("'" + (name ? name->getStringRef() : "any") + "' Pipeline").str(); })); } void runAfterPipeline(std::optional, const PipelineParentInfo &) override { auto &activeTimers = activeThreadTimers[llvm::get_threadid()]; assert(!activeTimers.empty() && "expected active timer"); activeTimers.pop_back(); } //===--------------------------------------------------------------------===// // Pass //===--------------------------------------------------------------------===// void runBeforePass(Pass *pass, Operation *) override { auto tid = llvm::get_threadid(); auto &activeTimers = activeThreadTimers[tid]; auto &parentScope = activeTimers.empty() ? rootScope : activeTimers.back(); if (auto *adaptor = dyn_cast(pass)) { parentTimerIndices[{tid, pass}] = activeTimers.size(); auto scope = parentScope.nest(pass->getThreadingSiblingOrThis(), [adaptor]() { return adaptor->getAdaptorName(); }); if (adaptor->getPassManagers().size() <= 1) scope.hide(); activeTimers.push_back(std::move(scope)); } else { activeTimers.push_back( parentScope.nest(pass->getThreadingSiblingOrThis(), [pass]() { return std::string(pass->getName()); })); } } void runAfterPass(Pass *pass, Operation *) override { auto tid = llvm::get_threadid(); if (isa(pass)) parentTimerIndices.erase({tid, pass}); auto &activeTimers = activeThreadTimers[tid]; assert(!activeTimers.empty() && "expected active timer"); activeTimers.pop_back(); } void runAfterPassFailed(Pass *pass, Operation *op) override { runAfterPass(pass, op); } //===--------------------------------------------------------------------===// // Analysis //===--------------------------------------------------------------------===// void runBeforeAnalysis(StringRef name, TypeID id, Operation *) override { auto tid = llvm::get_threadid(); auto &activeTimers = activeThreadTimers[tid]; auto &parentScope = activeTimers.empty() ? rootScope : activeTimers.back(); activeTimers.push_back(parentScope.nest( id.getAsOpaquePointer(), [name] { return "(A) " + name.str(); })); } void runAfterAnalysis(StringRef, TypeID, Operation *) override { auto &activeTimers = activeThreadTimers[llvm::get_threadid()]; assert(!activeTimers.empty() && "expected active timer"); activeTimers.pop_back(); } }; } // namespace //===----------------------------------------------------------------------===// // PassManager //===----------------------------------------------------------------------===// /// Add an instrumentation to time the execution of passes and the computation /// of analyses. void PassManager::enableTiming(TimingScope &timingScope) { if (!timingScope) return; addInstrumentation(std::make_unique(timingScope)); } /// Add an instrumentation to time the execution of passes and the computation /// of analyses. void PassManager::enableTiming(std::unique_ptr tm) { if (!tm->getRootTimer()) return; // no need to keep the timing manager around if it's disabled addInstrumentation(std::make_unique(std::move(tm))); } /// Add an instrumentation to time the execution of passes and the computation /// of analyses. void PassManager::enableTiming() { auto tm = std::make_unique(); tm->setEnabled(true); enableTiming(std::move(tm)); }