/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 | 84.2k | void PostOrderAnalysis::visitPostOrder(BasicBlock *BB, BlockList &order) { |
29 | 84.2k | struct State { |
30 | 84.2k | BasicBlock *BB; |
31 | 84.2k | succ_iterator cur, end; |
32 | 84.2k | explicit State(BasicBlock *BB) |
33 | 340k | : BB(BB), cur(succ_begin(BB)), end(succ_end(BB)) {} |
34 | 84.2k | }; |
35 | | |
36 | 84.2k | llvh::SmallPtrSet<BasicBlock *, 16> visited{}; |
37 | 84.2k | llvh::SmallVector<State, 32> stack{}; |
38 | | |
39 | 84.2k | stack.emplace_back(BB); |
40 | 340k | do { |
41 | 624k | while (stack.back().cur != stack.back().end) { |
42 | 284k | BB = *stack.back().cur++; |
43 | 284k | if (visited.insert(BB).second) |
44 | 256k | stack.emplace_back(BB); |
45 | 284k | } |
46 | | |
47 | 340k | order.push_back(stack.back().BB); |
48 | 340k | stack.pop_back(); |
49 | 340k | } while (!stack.empty()); |
50 | 84.2k | } |
51 | | |
52 | 84.2k | PostOrderAnalysis::PostOrderAnalysis(Function *F) : ctx_(F->getContext()) { |
53 | 84.2k | assert(Order.empty() && "vector must be empty"); |
54 | | |
55 | 84.2k | BasicBlock *entry = &*F->begin(); |
56 | | |
57 | | // Finally, do an PO scan from the entry block. |
58 | 84.2k | visitPostOrder(entry, Order); |
59 | | |
60 | 84.2k | assert( |
61 | 84.2k | !Order.empty() && Order[Order.size() - 1] == entry && |
62 | 84.2k | "Entry block must be the last element in the vector"); |
63 | 84.2k | } |
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 | 242 | static llvh::Optional<int> &nextScopeDepth(llvh::Optional<int> &depth) { |
211 | 242 | if (depth) { |
212 | 234 | *depth -= 1; |
213 | 234 | } |
214 | 242 | return depth; |
215 | 242 | } |
216 | | |
217 | | FunctionScopeAnalysis::ScopeData |
218 | | FunctionScopeAnalysis::calculateFunctionScopeData( |
219 | | ScopeDesc *scopeDesc, |
220 | 550 | llvh::Optional<int> depth) { |
221 | 550 | auto entry = lexicalScopeDescMap_.find(scopeDesc); |
222 | 550 | if (entry != lexicalScopeDescMap_.end()) { |
223 | 74 | return entry->second; |
224 | 74 | } |
225 | | |
226 | 476 | if (!scopeDesc->hasFunction()) { |
227 | | // The only scope that doesn't have a function is the Module's initial |
228 | | // scope. |
229 | 234 | assert(scopeDesc->getParent() == nullptr); |
230 | 242 | } else { |
231 | | // If the function is a CommonJS module, |
232 | | // then it won't have a CreateFunctionInst, so calculate the depth manually. |
233 | 242 | Function *F = scopeDesc->getFunction(); |
234 | 242 | Module *module = F->getParent(); |
235 | 242 | if (module->findCJSModule(F)) { |
236 | 0 | return ScopeData{1, false}; |
237 | 0 | } |
238 | 242 | } |
239 | | |
240 | 476 | ScopeData ret = ScopeData::orphan(); |
241 | 476 | if (ScopeDesc *parentScope = scopeDesc->getParent()) { |
242 | 242 | ScopeData parentData = |
243 | 242 | calculateFunctionScopeData(parentScope, nextScopeDepth(depth)); |
244 | 242 | if (!parentData.orphaned && (parentData.depth >= 0 || depth)) { |
245 | 242 | assert(!depth || (depth == parentData.depth)); |
246 | 242 | ret = ScopeData(parentData.depth + 1); |
247 | 242 | } |
248 | 242 | } else if (depth) { |
249 | 234 | ret = ScopeData(*depth); |
250 | 234 | } |
251 | | |
252 | 476 | LLVM_DEBUG({ |
253 | 476 | if (ret.orphaned) { |
254 | 476 | dbgs() << "Orphaned scope in function \"" |
255 | 476 | << scopeDesc->getFunction()->getInternalNameStr() << "\"\n"; |
256 | 476 | } |
257 | 476 | }); |
258 | | |
259 | 476 | lexicalScopeDescMap_[scopeDesc] = ret; |
260 | 476 | return ret; |
261 | 476 | } |
262 | | |
263 | 74 | Optional<int32_t> FunctionScopeAnalysis::getScopeDepth(ScopeDesc *S) { |
264 | 74 | ScopeData sd = calculateFunctionScopeData(S); |
265 | 74 | if (sd.orphaned) |
266 | 0 | return llvh::None; |
267 | 74 | return sd.depth; |
268 | 74 | } |
269 | | |
270 | | #undef DEBUG_TYPE |