/src/hermes/lib/IR/Analysis.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #include "hermes/IR/Analysis.h" |
9 | | #include "hermes/IR/CFG.h" |
10 | | #include "hermes/IR/IR.h" |
11 | | #include "hermes/Utils/Dumper.h" |
12 | | #include "llvh/ADT/PriorityQueue.h" |
13 | | #include "llvh/Support/Debug.h" |
14 | | |
15 | | #include <utility> |
16 | | |
17 | | #ifdef DEBUG_TYPE |
18 | | #undef DEBUG_TYPE |
19 | | #endif |
20 | | #define DEBUG_TYPE "IR Analysis" |
21 | | |
22 | | using namespace hermes; |
23 | | |
24 | | using llvh::dbgs; |
25 | | using llvh::Optional; |
26 | | using llvh::outs; |
27 | | |
28 | 342k | void PostOrderAnalysis::visitPostOrder(BasicBlock *BB, BlockList &order) { |
29 | 342k | struct State { |
30 | 342k | BasicBlock *BB; |
31 | 342k | succ_iterator cur, end; |
32 | 342k | explicit State(BasicBlock *BB) |
33 | 461k | : BB(BB), cur(succ_begin(BB)), end(succ_end(BB)) {} |
34 | 342k | }; |
35 | | |
36 | 342k | llvh::SmallPtrSet<BasicBlock *, 16> visited{}; |
37 | 342k | llvh::SmallVector<State, 32> stack{}; |
38 | | |
39 | 342k | stack.emplace_back(BB); |
40 | 461k | do { |
41 | 580k | while (stack.back().cur != stack.back().end) { |
42 | 118k | BB = *stack.back().cur++; |
43 | 118k | if (visited.insert(BB).second) |
44 | 118k | stack.emplace_back(BB); |
45 | 118k | } |
46 | | |
47 | 461k | order.push_back(stack.back().BB); |
48 | 461k | stack.pop_back(); |
49 | 461k | } while (!stack.empty()); |
50 | 342k | } |
51 | | |
52 | 342k | PostOrderAnalysis::PostOrderAnalysis(Function *F) : ctx_(F->getContext()) { |
53 | 342k | assert(Order.empty() && "vector must be empty"); |
54 | | |
55 | 0 | BasicBlock *entry = &*F->begin(); |
56 | | |
57 | | // Finally, do an PO scan from the entry block. |
58 | 342k | visitPostOrder(entry, Order); |
59 | | |
60 | 342k | assert( |
61 | 342k | !Order.empty() && Order[Order.size() - 1] == entry && |
62 | 342k | "Entry block must be the last element in the vector"); |
63 | 342k | } |
64 | | |
65 | 0 | void PostOrderAnalysis::dump() { |
66 | 0 | IRPrinter D(ctx_, outs()); |
67 | 0 | D.visit(*Order[0]->getParent()); |
68 | |
|
69 | 0 | outs() << "Blocks: "; |
70 | |
|
71 | 0 | for (auto &BB : Order) { |
72 | 0 | outs() << "BB" << D.BBNamer.getNumber(BB) << " "; |
73 | 0 | } |
74 | |
|
75 | 0 | outs() << "\n"; |
76 | 0 | } |
77 | | |
78 | | // Perform depth-first search to identify loops. The loop header of a block B is |
79 | | // its DFS ancestor H such that a descendent of B has a back edge to H. (In the |
80 | | // case of a self-loop, the ancestor and descendant are B itself.) When there |
81 | | // are multiple such blocks, we take the one with the maximum DFS discovery time |
82 | | // to get the innermost loop. The preheader is the DFS parent of H. We use |
83 | | // DominanceInfo to ensure that preheaders dominate headers and headers dominate |
84 | | // all blocks in the loop. |
85 | 0 | LoopAnalysis::LoopAnalysis(Function *F, const DominanceInfo &dominanceInfo) { |
86 | | // BlockMap (defined in Analysis.h) and BlockSet are used for most cases. |
87 | | // TinyBlockSet is only used for headerSets (defined below); it has a smaller |
88 | | // inline size because we store BLOCK -> {SET OF HEADERS} for every block, and |
89 | | // very deeply nested loops (leading to many headers) are not that common. |
90 | 0 | using BlockSet = llvh::SmallPtrSet<const BasicBlock *, 16>; |
91 | 0 | using TinyBlockSet = llvh::SmallPtrSet<BasicBlock *, 2>; |
92 | |
|
93 | 0 | int dfsTime = 0; |
94 | | // Maps each block to its DFS discovery time (value of dfsTime). |
95 | 0 | BlockMap<int> discovered; |
96 | | // Set of blocks we have finished visiting. |
97 | 0 | BlockSet finished; |
98 | | // Maps each block to its parent in the DFS tree. |
99 | 0 | BlockMap<BasicBlock *> parent; |
100 | | // Maps each block to a set of header blocks of loops that enclose it. |
101 | 0 | BlockMap<TinyBlockSet> headerSets; |
102 | | |
103 | | // Explicit stack for depth-first search. |
104 | 0 | llvh::SmallVector<BasicBlock *, 16> stack; |
105 | 0 | stack.push_back(&*F->begin()); |
106 | 0 | while (stack.size()) { |
107 | 0 | BasicBlock *BB = stack.back(); |
108 | | // If it's the first time visiting, record the discovery time and push all |
109 | | // undiscovered successors onto the stack. Leave BB on the stack so that |
110 | | // after visiting all descendants, we come back to it and resume below. |
111 | 0 | if (discovered.try_emplace(BB, dfsTime).second) { |
112 | 0 | ++dfsTime; |
113 | 0 | for (auto it = succ_begin(BB), e = succ_end(BB); it != e; ++it) { |
114 | 0 | BasicBlock *succ = *it; |
115 | 0 | if (!discovered.count(succ)) { |
116 | 0 | stack.push_back(succ); |
117 | 0 | parent[succ] = BB; |
118 | 0 | } |
119 | 0 | } |
120 | 0 | continue; |
121 | 0 | } |
122 | | |
123 | 0 | stack.pop_back(); |
124 | 0 | if (finished.count(BB)) { |
125 | | // BB was duplicated on the stack and we already finished visiting it. |
126 | 0 | continue; |
127 | 0 | } |
128 | | |
129 | | // Check back/forward/cross edges to find loops BB is in. |
130 | 0 | TinyBlockSet headers; |
131 | 0 | for (auto it = succ_begin(BB), e = succ_end(BB); it != e; ++it) { |
132 | 0 | BasicBlock *succ = *it; |
133 | 0 | assert(discovered.count(succ) && "Unexpected undiscovered successor"); |
134 | 0 | if (!finished.count(succ)) { |
135 | | // Found a back edge to a header block. |
136 | 0 | headers.insert(succ); |
137 | 0 | } else { |
138 | | // Either a forward edge or cross edge. Headers of succ are also headers |
139 | | // of BB if we haven't finished visiting them. |
140 | 0 | auto entry = headerSets.find(succ); |
141 | 0 | if (entry != headerSets.end()) { |
142 | 0 | for (BasicBlock *headerOfSucc : entry->second) { |
143 | 0 | if (!finished.count(headerOfSucc)) { |
144 | 0 | headers.insert(headerOfSucc); |
145 | 0 | } |
146 | 0 | } |
147 | 0 | } |
148 | 0 | } |
149 | 0 | } |
150 | 0 | if (!headers.empty()) { |
151 | 0 | auto insert = headerSets.try_emplace(BB, std::move(headers)); |
152 | 0 | (void)insert; |
153 | 0 | assert(insert.second && "Inserting headers for same block twice!"); |
154 | 0 | } |
155 | 0 | finished.insert(BB); |
156 | 0 | } |
157 | | |
158 | | // Determine which headers are good/bad and populate headerToPreheader_ using |
159 | | // the parent mapping. A header is good if it dominates every block in the |
160 | | // loop (that is, it is the only entry point). |
161 | 0 | BlockSet badHeaders; |
162 | 0 | for (auto &entry : headerSets) { |
163 | 0 | const BasicBlock *BB = entry.first; |
164 | 0 | for (const BasicBlock *header : entry.second) { |
165 | 0 | if (badHeaders.count(header)) { |
166 | 0 | continue; |
167 | 0 | } |
168 | 0 | if (!dominanceInfo.dominates(header, BB)) { |
169 | 0 | badHeaders.insert(header); |
170 | 0 | } else if (!headerToPreheader_.count(header)) { |
171 | 0 | BasicBlock *preheader = parent[header]; |
172 | 0 | if (dominanceInfo.properlyDominates(preheader, header)) { |
173 | 0 | headerToPreheader_[header] = preheader; |
174 | 0 | } |
175 | 0 | } |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | | // Populate blockToHeader_ with the innermost loop header for each block. |
180 | 0 | for (auto &entry : headerSets) { |
181 | 0 | const BasicBlock *BB = entry.first; |
182 | 0 | TinyBlockSet &headers = entry.second; |
183 | 0 | if (!headers.empty()) { |
184 | 0 | BasicBlock *innerHeader = nullptr; |
185 | 0 | int maxDiscovery = -1; |
186 | 0 | for (BasicBlock *header : headers) { |
187 | 0 | int discovery = discovered[header]; |
188 | 0 | if (discovery > maxDiscovery && !badHeaders.count(header)) { |
189 | 0 | maxDiscovery = discovery; |
190 | 0 | innerHeader = header; |
191 | 0 | } |
192 | 0 | } |
193 | 0 | blockToHeader_[BB] = innerHeader; |
194 | 0 | } |
195 | 0 | } |
196 | 0 | } |
197 | | |
198 | 0 | BasicBlock *LoopAnalysis::getLoopHeader(const BasicBlock *BB) const { |
199 | 0 | return blockToHeader_.lookup(BB); |
200 | 0 | } |
201 | | |
202 | 0 | BasicBlock *LoopAnalysis::getLoopPreheader(const BasicBlock *BB) const { |
203 | 0 | BasicBlock *header = getLoopHeader(BB); |
204 | 0 | if (header) { |
205 | 0 | return headerToPreheader_.lookup(header); |
206 | 0 | } |
207 | 0 | return nullptr; |
208 | 0 | } |
209 | | |
210 | 114k | static llvh::Optional<int> &nextScopeDepth(llvh::Optional<int> &depth) { |
211 | 114k | if (depth) { |
212 | 56 | *depth -= 1; |
213 | 56 | } |
214 | 114k | return depth; |
215 | 114k | } |
216 | | |
217 | | Function *FunctionScopeAnalysis::computeParent( |
218 | | ScopeDesc *thisScope, |
219 | | ScopeDesc *parentScope, |
220 | 114k | const ScopeData &sd) { |
221 | 114k | return (parentScope->hasFunction() && |
222 | 114k | parentScope->getFunction() != thisScope->getFunction()) |
223 | 114k | ? parentScope->getFunction() |
224 | 114k | : sd.parent; |
225 | 114k | } |
226 | | |
227 | | FunctionScopeAnalysis::ScopeData |
228 | | FunctionScopeAnalysis::calculateFunctionScopeData( |
229 | | ScopeDesc *scopeDesc, |
230 | 228k | llvh::Optional<int> depth) { |
231 | 228k | auto entry = lexicalScopeDescMap_.find(scopeDesc); |
232 | 228k | if (entry != lexicalScopeDescMap_.end()) { |
233 | 114k | return entry->second; |
234 | 114k | } |
235 | | |
236 | 114k | if (!scopeDesc->hasFunction()) { |
237 | | // The only scope that doesn't have a function is the Module's initial |
238 | | // scope. |
239 | 56 | assert(scopeDesc->getParent() == nullptr); |
240 | 114k | } else { |
241 | | // If the function is a CommonJS module, |
242 | | // then it won't have a CreateFunctionInst, so calculate the depth manually. |
243 | 114k | Function *F = scopeDesc->getFunction(); |
244 | 114k | Module *module = F->getParent(); |
245 | 114k | if (module->findCJSModule(F)) { |
246 | 0 | return ScopeData{module->getTopLevelFunction(), 1, false}; |
247 | 0 | } |
248 | 114k | } |
249 | | |
250 | 114k | ScopeData ret = ScopeData::orphan(); |
251 | 114k | if (ScopeDesc *parentScope = scopeDesc->getParent()) { |
252 | 114k | ScopeData parentData = |
253 | 114k | calculateFunctionScopeData(parentScope, nextScopeDepth(depth)); |
254 | 114k | if (!parentData.orphaned && (parentData.depth >= 0 || depth)) { |
255 | 114k | assert(!depth || (depth == parentData.depth)); |
256 | 0 | ret = ScopeData( |
257 | 114k | computeParent(scopeDesc, parentScope, parentData), |
258 | 114k | parentData.depth + 1); |
259 | 114k | } |
260 | 114k | } else if (depth) { |
261 | 56 | ret = ScopeData(nullptr, *depth); |
262 | 56 | } |
263 | | |
264 | 114k | LLVM_DEBUG({ |
265 | 114k | if (ret.orphaned) { |
266 | 114k | dbgs() << "Orphaned scope in function \"" |
267 | 114k | << scopeDesc->getFunction()->getInternalNameStr() << "\"\n"; |
268 | 114k | } |
269 | 114k | }); |
270 | | |
271 | 114k | lexicalScopeDescMap_[scopeDesc] = ret; |
272 | 114k | return ret; |
273 | 114k | } |
274 | | |
275 | 0 | Optional<int32_t> FunctionScopeAnalysis::getScopeDepth(ScopeDesc *S) { |
276 | 0 | ScopeData sd = calculateFunctionScopeData(S); |
277 | 0 | if (sd.orphaned) |
278 | 0 | return llvh::None; |
279 | 0 | return sd.depth; |
280 | 0 | } |
281 | | |
282 | 114k | Function *FunctionScopeAnalysis::getLexicalParent(Function *F) { |
283 | 114k | return calculateFunctionScopeData(F->getFunctionScopeDesc()).parent; |
284 | 114k | } |
285 | | |
286 | | #undef DEBUG_TYPE |