/src/hermes/lib/BCGen/Exceptions.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 | | #define DEBUG_TYPE "exceptions" |
9 | | |
10 | | #include "hermes/BCGen/Exceptions.h" |
11 | | #include "hermes/IR/CFG.h" |
12 | | #include "hermes/IR/IR.h" |
13 | | #include "hermes/IR/IRBuilder.h" |
14 | | |
15 | | #include "llvh/Support/raw_ostream.h" |
16 | | |
17 | | using namespace hermes; |
18 | | |
19 | | /// Construct the list of basic blocks covered by each catch instruction. |
20 | | /// Use recursion to handle nested catches. |
21 | | llvh::Optional<llvh::SmallPtrSet<BasicBlock *, 4>> hermes::constructCatchMap( |
22 | | Function *F, |
23 | | CatchInfoMap &catchInfoMap, |
24 | | llvh::SmallVectorImpl<CatchInst *> &aliveCatches, |
25 | | llvh::SmallPtrSetImpl<BasicBlock *> &visited, |
26 | | BasicBlock *startBlock, |
27 | 7.36k | uint32_t maxRecursionDepth) { |
28 | 7.36k | assert( |
29 | 7.36k | !llvh::isa<CatchInst>(startBlock->front()) && |
30 | 7.36k | "Functions and try bodies should never start with a catch instruction."); |
31 | 7.36k | if (maxRecursionDepth == 0) { |
32 | 0 | F->getContext().getSourceErrorManager().error( |
33 | 0 | F->getSourceRange(), "Too deeply nested try/catch"); |
34 | 0 | return llvh::None; |
35 | 0 | } |
36 | | |
37 | | // Stack to DFS through the CFG. |
38 | 7.36k | llvh::SmallVector<BasicBlock *, 4> stack; |
39 | | // Track each of the basic blocks that start with a TryEndInst corresponding |
40 | | // to the current try. |
41 | 7.36k | llvh::SmallPtrSet<BasicBlock *, 4> tryEndBlocks; |
42 | | |
43 | 7.36k | visited.insert(startBlock); |
44 | 7.36k | stack.push_back(startBlock); |
45 | 181k | while (!stack.empty()) { |
46 | 174k | BasicBlock *currentBlock = stack.pop_back_val(); |
47 | 174k | assert( |
48 | 174k | visited.count(currentBlock) && |
49 | 174k | "All blocks on the stack must be marked as visited."); |
50 | | |
51 | | // For every nested try that's alive, we add the basic block into it. |
52 | 174k | for (const auto &aliveCatch : aliveCatches) |
53 | 1.86M | catchInfoMap[aliveCatch].coveredBlockList.push_back(currentBlock); |
54 | | |
55 | 174k | auto *tryStartInst = |
56 | 174k | llvh::dyn_cast<TryStartInst>(currentBlock->getTerminator()); |
57 | | |
58 | 174k | if (!tryStartInst) { |
59 | | // Common case: no TryStartInst, we add successors to the stack. |
60 | 179k | for (BasicBlock *successor : successors(currentBlock)) { |
61 | | // If this block marks the end of the try, then add it to tryEndBlocks, |
62 | | // but not to the stack. It will be visited by the caller. |
63 | | // Otherwise, add unvisited blocks to the stack. |
64 | 179k | if (llvh::isa<TryEndInst>(&successor->front())) |
65 | 7.35k | tryEndBlocks.insert(successor); |
66 | 171k | else if (visited.insert(successor).second) |
67 | 152k | stack.push_back(successor); |
68 | 179k | } |
69 | 166k | continue; |
70 | 166k | } |
71 | | |
72 | | // Hit a TryStartInst, marking the start of a new try region. |
73 | | // The first instruction of the catch target block must be CatchInst. |
74 | 7.35k | auto catchInst = cast<CatchInst>(&tryStartInst->getCatchTarget()->front()); |
75 | 7.35k | catchInfoMap[catchInst].depth = aliveCatches.size(); |
76 | | |
77 | | // Pushing the CatchInst to the try stack, and continue scan the try body. |
78 | 7.35k | aliveCatches.push_back(catchInst); |
79 | 7.35k | auto endBlocks = constructCatchMap( |
80 | 7.35k | F, |
81 | 7.35k | catchInfoMap, |
82 | 7.35k | aliveCatches, |
83 | 7.35k | visited, |
84 | 7.35k | tryStartInst->getTryBody(), |
85 | 7.35k | maxRecursionDepth - 1); |
86 | 7.35k | if (!endBlocks) |
87 | 0 | return llvh::None; |
88 | 7.35k | aliveCatches.pop_back(); |
89 | | |
90 | 7.35k | for (BasicBlock *endBlock : *endBlocks) { |
91 | 7.35k | assert( |
92 | 7.35k | visited.count(endBlock) == 0 && |
93 | 7.35k | "End blocks must be dominated by the try start."); |
94 | 7.35k | assert( |
95 | 7.35k | llvh::isa<TryEndInst>(&endBlock->front()) && |
96 | 7.35k | "End blocks must start with TryEndInst."); |
97 | 7.35k | visited.insert(endBlock); |
98 | 7.35k | stack.push_back(endBlock); |
99 | 7.35k | } |
100 | | |
101 | | // We also want to continue scan into the catch blocks. |
102 | 7.35k | BasicBlock *catchTarget = tryStartInst->getCatchTarget(); |
103 | 7.35k | assert( |
104 | 7.35k | visited.count(catchTarget) == 0 && |
105 | 7.35k | "Catch block must be dominated by the try start."); |
106 | 7.35k | visited.insert(catchTarget); |
107 | 7.35k | stack.push_back(catchTarget); |
108 | 7.35k | } |
109 | 7.36k | assert( |
110 | 7.36k | (aliveCatches.size() || !tryEndBlocks.size()) && |
111 | 7.36k | "Block without live catches cannot have TryEndInst."); |
112 | 7.36k | return tryEndBlocks; |
113 | 7.36k | } |
114 | | |
115 | | ExceptionEntryList hermes::generateExceptionHandlers( |
116 | | CatchInfoMap &catchInfoMap, |
117 | | BasicBlockInfoMap &bbMap, |
118 | 8 | Function *F) { |
119 | | // Construct the list of blocks and depth covered by each CatchInst. |
120 | 8 | llvh::SmallVector<CatchInst *, 4> aliveCatches{}; |
121 | 8 | llvh::SmallPtrSet<BasicBlock *, 32> visited{}; |
122 | 8 | static constexpr uint32_t MAX_RECURSION_DEPTH = 1024; |
123 | 8 | if (!constructCatchMap( |
124 | 8 | F, |
125 | 8 | catchInfoMap, |
126 | 8 | aliveCatches, |
127 | 8 | visited, |
128 | 8 | &F->front(), |
129 | 8 | MAX_RECURSION_DEPTH)) |
130 | 0 | return {}; |
131 | | |
132 | 8 | ExceptionEntryList exception_entries; |
133 | 7.35k | for (auto I : catchInfoMap) { |
134 | 7.35k | auto &catchInfo = I.second; |
135 | | // The basic blocks covered by a catch instruction may not be continuous. |
136 | | // For each basic block, we walk through the current list of ranges, |
137 | | // and try to merge them into a minimum number of ranges. |
138 | 7.35k | llvh::SmallVector<std::pair<uint32_t, uint32_t>, 4> catch_ranges; |
139 | 1.86M | for (auto BB : catchInfo.coveredBlockList) { |
140 | 1.86M | auto it = bbMap.find(BB); |
141 | 1.86M | assert(it != bbMap.end() && "Basic Block missing."); |
142 | | |
143 | 1.86M | auto resolved_loc = it->second; |
144 | 1.86M | if (resolved_loc.first == resolved_loc.second) { |
145 | | // Empty basic block, skip. |
146 | 586k | continue; |
147 | 586k | } |
148 | 1.28M | catch_ranges.push_back(resolved_loc); |
149 | 1.28M | } |
150 | 7.35k | std::sort(catch_ranges.begin(), catch_ranges.end()); |
151 | | // After ranges are sorted, a range could only be merged with it's |
152 | | // previous range, if they are adjacent. |
153 | | // Note: no range can overlap, as basic blocks do not overlap. |
154 | 7.35k | int nextIndex = 0; |
155 | 1.28M | for (auto resolved_loc : catch_ranges) { |
156 | | // If we are looking at the first range, or the range cannot |
157 | | // be merged with the previous range, we store this range. |
158 | 1.28M | if (nextIndex == 0 || |
159 | 1.28M | catch_ranges[nextIndex - 1].second != resolved_loc.first) { |
160 | 6.94k | catch_ranges[nextIndex++] = resolved_loc; |
161 | 6.94k | continue; |
162 | 6.94k | } |
163 | | // Otherwise we merge with the previous range. |
164 | 1.27M | catch_ranges[nextIndex - 1].second = resolved_loc.second; |
165 | 1.27M | } |
166 | | // The merging happened in-place. Hence we need to throw away the rest. |
167 | 7.35k | catch_ranges.resize(nextIndex); |
168 | | // For each range, we register it as an exception handler entry. |
169 | 7.35k | for (auto range : catch_ranges) { |
170 | 6.94k | exception_entries.push_back( |
171 | 6.94k | {(uint32_t)range.first, |
172 | 6.94k | (uint32_t)range.second, |
173 | 6.94k | (uint32_t)catchInfo.catchLocation, |
174 | 6.94k | catchInfo.depth}); |
175 | 6.94k | } |
176 | 7.35k | } |
177 | | // Sort ranges by depth. In hermes, depth increase when you nest try inside |
178 | | // try/catch/finally. |
179 | 8 | std::sort(exception_entries.begin(), exception_entries.end()); |
180 | 8 | return exception_entries; |
181 | 8 | } |
182 | | |
183 | | #undef DEBUG_TYPE |