Coverage Report

Created: 2025-06-24 06:43

/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