1 | //===- PassTiming.cpp -----------------------------------------------------===// |
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 | |
9 | #include "PassDetail.h" |
10 | #include "mlir/Pass/PassManager.h" |
11 | #include "llvm/ADT/SmallVector.h" |
12 | #include "llvm/Support/Threading.h" |
13 | |
14 | #include <chrono> |
15 | #include <optional> |
16 | |
17 | using namespace mlir; |
18 | using namespace mlir::detail; |
19 | |
20 | //===----------------------------------------------------------------------===// |
21 | // PassTiming |
22 | //===----------------------------------------------------------------------===// |
23 | |
24 | namespace { |
25 | struct PassTiming : public PassInstrumentation { |
26 | PassTiming(TimingScope &timingScope) : rootScope(timingScope) {} |
27 | PassTiming(std::unique_ptr<TimingManager> tm) |
28 | : ownedTimingManager(std::move(tm)), |
29 | ownedTimingScope(ownedTimingManager->getRootScope()), |
30 | rootScope(ownedTimingScope) {} |
31 | |
32 | /// If a pass can spawn additional work on other threads, it records the |
33 | /// index to its currently active timer here. Passes that run on a |
34 | /// newly-forked thread will check this list to find the active timer of the |
35 | /// parent thread into which the new thread should be nested. |
36 | DenseMap<PipelineParentInfo, unsigned> parentTimerIndices; |
37 | |
38 | /// A stack of the currently active timing scopes per thread. |
39 | DenseMap<uint64_t, SmallVector<TimingScope, 4>> activeThreadTimers; |
40 | |
41 | /// The timing manager owned by this instrumentation (in case timing was |
42 | /// enabled by the user on the pass manager without providing an external |
43 | /// timing manager). This *must* appear before the `ownedTimingScope` to |
44 | /// ensure the timing manager is destroyed *after* the scope, since the latter |
45 | /// may hold a timer that points into the former. |
46 | std::unique_ptr<TimingManager> ownedTimingManager; |
47 | TimingScope ownedTimingScope; |
48 | |
49 | /// The root timing scope into which timing is reported. |
50 | TimingScope &rootScope; |
51 | |
52 | //===--------------------------------------------------------------------===// |
53 | // Pipeline |
54 | //===--------------------------------------------------------------------===// |
55 | |
56 | void runBeforePipeline(std::optional<OperationName> name, |
57 | const PipelineParentInfo &parentInfo) override { |
58 | auto tid = llvm::get_threadid(); |
59 | auto &activeTimers = activeThreadTimers[tid]; |
60 | |
61 | // Find the parent scope, either using the parent info or the root scope |
62 | // (e.g. in the case of the top-level pipeline). |
63 | TimingScope *parentScope; |
64 | auto it = parentTimerIndices.find(Val: parentInfo); |
65 | if (it != parentTimerIndices.end()) |
66 | parentScope = &activeThreadTimers[parentInfo.parentThreadID][it->second]; |
67 | else |
68 | parentScope = &rootScope; |
69 | |
70 | // Use nullptr to anchor op-agnostic pipelines, otherwise use the name of |
71 | // the operation. |
72 | const void *timerId = name ? name->getAsOpaquePointer() : nullptr; |
73 | activeTimers.push_back(Elt: parentScope->nest(args: timerId, args: [name] { |
74 | return ("'" + (name ? name->getStringRef() : "any" ) + "' Pipeline" ).str(); |
75 | })); |
76 | } |
77 | |
78 | void runAfterPipeline(std::optional<OperationName>, |
79 | const PipelineParentInfo &) override { |
80 | auto &activeTimers = activeThreadTimers[llvm::get_threadid()]; |
81 | assert(!activeTimers.empty() && "expected active timer" ); |
82 | activeTimers.pop_back(); |
83 | } |
84 | |
85 | //===--------------------------------------------------------------------===// |
86 | // Pass |
87 | //===--------------------------------------------------------------------===// |
88 | |
89 | void runBeforePass(Pass *pass, Operation *) override { |
90 | auto tid = llvm::get_threadid(); |
91 | auto &activeTimers = activeThreadTimers[tid]; |
92 | auto &parentScope = activeTimers.empty() ? rootScope : activeTimers.back(); |
93 | |
94 | if (auto *adaptor = dyn_cast<OpToOpPassAdaptor>(Val: pass)) { |
95 | parentTimerIndices[{.parentThreadID: tid, .parentPass: pass}] = activeTimers.size(); |
96 | auto scope = |
97 | parentScope.nest(args: pass->getThreadingSiblingOrThis(), |
98 | args: [adaptor]() { return adaptor->getAdaptorName(); }); |
99 | if (adaptor->getPassManagers().size() <= 1) |
100 | scope.hide(); |
101 | activeTimers.push_back(Elt: std::move(scope)); |
102 | } else { |
103 | activeTimers.push_back( |
104 | Elt: parentScope.nest(args: pass->getThreadingSiblingOrThis(), |
105 | args: [pass]() { return std::string(pass->getName()); })); |
106 | } |
107 | } |
108 | |
109 | void runAfterPass(Pass *pass, Operation *) override { |
110 | auto tid = llvm::get_threadid(); |
111 | if (isa<OpToOpPassAdaptor>(Val: pass)) |
112 | parentTimerIndices.erase(Val: {.parentThreadID: tid, .parentPass: pass}); |
113 | auto &activeTimers = activeThreadTimers[tid]; |
114 | assert(!activeTimers.empty() && "expected active timer" ); |
115 | activeTimers.pop_back(); |
116 | } |
117 | |
118 | void runAfterPassFailed(Pass *pass, Operation *op) override { |
119 | runAfterPass(pass, op); |
120 | } |
121 | |
122 | //===--------------------------------------------------------------------===// |
123 | // Analysis |
124 | //===--------------------------------------------------------------------===// |
125 | |
126 | void runBeforeAnalysis(StringRef name, TypeID id, Operation *) override { |
127 | auto tid = llvm::get_threadid(); |
128 | auto &activeTimers = activeThreadTimers[tid]; |
129 | auto &parentScope = activeTimers.empty() ? rootScope : activeTimers.back(); |
130 | activeTimers.push_back(Elt: parentScope.nest( |
131 | args: id.getAsOpaquePointer(), args: [name] { return "(A) " + name.str(); })); |
132 | } |
133 | |
134 | void runAfterAnalysis(StringRef, TypeID, Operation *) override { |
135 | auto &activeTimers = activeThreadTimers[llvm::get_threadid()]; |
136 | assert(!activeTimers.empty() && "expected active timer" ); |
137 | activeTimers.pop_back(); |
138 | } |
139 | }; |
140 | } // namespace |
141 | |
142 | //===----------------------------------------------------------------------===// |
143 | // PassManager |
144 | //===----------------------------------------------------------------------===// |
145 | |
146 | /// Add an instrumentation to time the execution of passes and the computation |
147 | /// of analyses. |
148 | void PassManager::enableTiming(TimingScope &timingScope) { |
149 | if (!timingScope) |
150 | return; |
151 | addInstrumentation(pi: std::make_unique<PassTiming>(args&: timingScope)); |
152 | } |
153 | |
154 | /// Add an instrumentation to time the execution of passes and the computation |
155 | /// of analyses. |
156 | void PassManager::enableTiming(std::unique_ptr<TimingManager> tm) { |
157 | if (!tm->getRootTimer()) |
158 | return; // no need to keep the timing manager around if it's disabled |
159 | addInstrumentation(pi: std::make_unique<PassTiming>(args: std::move(tm))); |
160 | } |
161 | |
162 | /// Add an instrumentation to time the execution of passes and the computation |
163 | /// of analyses. |
164 | void PassManager::enableTiming() { |
165 | auto tm = std::make_unique<DefaultTimingManager>(); |
166 | tm->setEnabled(true); |
167 | enableTiming(tm: std::move(tm)); |
168 | } |
169 | |