blob: 5045e8db0bc0098544c18fae801c7d1807366167 [file] [log] [blame]
Alex Light86fe9b82020-11-16 16:54:01 +00001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "execution_subgraph.h"
18
19#include <algorithm>
20#include <unordered_set>
21
22#include "android-base/macros.h"
23#include "base/arena_allocator.h"
24#include "base/arena_bit_vector.h"
25#include "base/globals.h"
26#include "base/scoped_arena_allocator.h"
27#include "nodes.h"
28
29namespace art {
30
31ExecutionSubgraph::ExecutionSubgraph(HGraph* graph,
32 bool analysis_possible,
33 ScopedArenaAllocator* allocator)
34 : graph_(graph),
35 allocator_(allocator),
36 allowed_successors_(analysis_possible ? graph_->GetBlocks().size() : 0,
37 ~(std::bitset<kMaxFilterableSuccessors> {}),
38 allocator_->Adapter(kArenaAllocLSA)),
39 unreachable_blocks_(
40 allocator_, analysis_possible ? graph_->GetBlocks().size() : 0, false, kArenaAllocLSA),
41 valid_(analysis_possible),
42 needs_prune_(false),
43 finalized_(false) {
44 if (valid_) {
45 DCHECK(std::all_of(graph->GetBlocks().begin(), graph->GetBlocks().end(), [](HBasicBlock* it) {
46 return it == nullptr || it->GetSuccessors().size() <= kMaxFilterableSuccessors;
47 }));
48 }
49}
50
51void ExecutionSubgraph::RemoveBlock(const HBasicBlock* to_remove) {
52 if (!valid_) {
53 return;
54 }
55 uint32_t id = to_remove->GetBlockId();
56 if (unreachable_blocks_.IsBitSet(id)) {
57 if (kIsDebugBuild) {
58 // This isn't really needed but it's good to have this so it functions as
59 // a DCHECK that we always call Prune after removing any block.
60 needs_prune_ = true;
61 }
62 return;
63 }
64 unreachable_blocks_.SetBit(id);
65 for (HBasicBlock* pred : to_remove->GetPredecessors()) {
66 std::bitset<kMaxFilterableSuccessors> allowed_successors {};
67 // ZipCount iterates over both the successors and the index of them at the same time.
68 for (auto [succ, i] : ZipCount(MakeIterationRange(pred->GetSuccessors()))) {
69 if (succ != to_remove) {
70 allowed_successors.set(i);
71 }
72 }
73 LimitBlockSuccessors(pred, allowed_successors);
74 }
75}
76
77// Removes sink nodes.
78void ExecutionSubgraph::Prune() {
79 if (UNLIKELY(!valid_)) {
80 return;
81 }
82 needs_prune_ = false;
83 // This is the record of the edges that were both (1) explored and (2) reached
84 // the exit node.
85 {
86 // Allocator for temporary values.
87 ScopedArenaAllocator temporaries(graph_->GetArenaStack());
88 ScopedArenaVector<std::bitset<kMaxFilterableSuccessors>> results(
89 graph_->GetBlocks().size(), temporaries.Adapter(kArenaAllocLSA));
90 unreachable_blocks_.ClearAllBits();
91 // TODO We should support infinite loops as well.
92 if (UNLIKELY(graph_->GetExitBlock() == nullptr)) {
93 // Infinite loop
94 valid_ = false;
95 return;
96 }
97 // Fills up the 'results' map with what we need to add to update
98 // allowed_successors in order to prune sink nodes.
99 bool start_reaches_end = false;
100 // This is basically a DFS of the graph with some edges skipped.
101 {
102 const size_t num_blocks = graph_->GetBlocks().size();
103 constexpr ssize_t kUnvisitedSuccIdx = -1;
104 ArenaBitVector visiting(&temporaries, num_blocks, false, kArenaAllocLSA);
105 // How many of the successors of each block we have already examined. This
106 // has three states.
107 // (1) kUnvisitedSuccIdx: we have not examined any edges,
108 // (2) 0 <= val < # of successors: we have examined 'val' successors/are
109 // currently examining successors_[val],
110 // (3) kMaxFilterableSuccessors: We have examined all of the successors of
111 // the block (the 'result' is final).
112 ScopedArenaVector<ssize_t> last_succ_seen(
113 num_blocks, kUnvisitedSuccIdx, temporaries.Adapter(kArenaAllocLSA));
114 // A stack of which blocks we are visiting in this DFS traversal. Does not
115 // include the current-block. Used with last_succ_seen to figure out which
116 // bits to set if we find a path to the end/loop.
117 ScopedArenaVector<uint32_t> current_path(temporaries.Adapter(kArenaAllocLSA));
118 // Just ensure we have enough space. The allocator will be cleared shortly
119 // anyway so this is fast.
120 current_path.reserve(num_blocks);
121 // Current block we are examining. Modified only by 'push_block' and 'pop_block'
122 const HBasicBlock* cur_block = graph_->GetEntryBlock();
123 // Used to note a recur where we will start iterating on 'blk' and save
124 // where we are. We must 'continue' immediately after this.
125 auto push_block = [&](const HBasicBlock* blk) {
126 DCHECK(std::find(current_path.cbegin(), current_path.cend(), cur_block->GetBlockId()) ==
127 current_path.end());
128 if (kIsDebugBuild) {
129 std::for_each(current_path.cbegin(), current_path.cend(), [&](auto id) {
130 DCHECK_GT(last_succ_seen[id], kUnvisitedSuccIdx) << id;
131 DCHECK_LT(last_succ_seen[id], static_cast<ssize_t>(kMaxFilterableSuccessors)) << id;
132 });
133 }
134 current_path.push_back(cur_block->GetBlockId());
135 visiting.SetBit(cur_block->GetBlockId());
136 cur_block = blk;
137 };
138 // Used to note that we have fully explored a block and should return back
139 // up. Sets cur_block appropriately. We must 'continue' immediately after
140 // calling this.
141 auto pop_block = [&]() {
142 if (UNLIKELY(current_path.empty())) {
143 // Should only happen if entry-blocks successors are exhausted.
144 DCHECK_GE(last_succ_seen[graph_->GetEntryBlock()->GetBlockId()],
145 static_cast<ssize_t>(graph_->GetEntryBlock()->GetSuccessors().size()));
146 cur_block = nullptr;
147 } else {
148 const HBasicBlock* last = graph_->GetBlocks()[current_path.back()];
149 visiting.ClearBit(current_path.back());
150 current_path.pop_back();
151 cur_block = last;
152 }
153 };
154 // Mark the current path as a path to the end. This is in contrast to paths
155 // that end in (eg) removed blocks.
156 auto propagate_true = [&]() {
157 for (uint32_t id : current_path) {
158 DCHECK_GT(last_succ_seen[id], kUnvisitedSuccIdx);
159 DCHECK_LT(last_succ_seen[id], static_cast<ssize_t>(kMaxFilterableSuccessors));
160 results[id].set(last_succ_seen[id]);
161 }
162 };
163 ssize_t num_entry_succ = graph_->GetEntryBlock()->GetSuccessors().size();
164 // As long as the entry-block has not explored all successors we still have
165 // work to do.
166 const uint32_t entry_block_id = graph_->GetEntryBlock()->GetBlockId();
167 while (num_entry_succ > last_succ_seen[entry_block_id]) {
168 DCHECK(cur_block != nullptr);
169 uint32_t id = cur_block->GetBlockId();
170 DCHECK((current_path.empty() && cur_block == graph_->GetEntryBlock()) ||
171 current_path.front() == graph_->GetEntryBlock()->GetBlockId())
172 << "current path size: " << current_path.size()
173 << " cur_block id: " << cur_block->GetBlockId() << " entry id "
174 << graph_->GetEntryBlock()->GetBlockId();
175 DCHECK(!visiting.IsBitSet(id))
176 << "Somehow ended up in a loop! This should have been caught before now! " << id;
177 std::bitset<kMaxFilterableSuccessors>& result = results[id];
178 if (cur_block == graph_->GetExitBlock()) {
179 start_reaches_end = true;
180 propagate_true();
181 pop_block();
182 continue;
183 } else if (last_succ_seen[id] == kMaxFilterableSuccessors) {
184 // Already fully explored.
185 if (result.any()) {
186 propagate_true();
187 }
188 pop_block();
189 continue;
190 }
191 // NB This is a pointer. Modifications modify the last_succ_seen.
192 ssize_t* cur_succ = &last_succ_seen[id];
193 std::bitset<kMaxFilterableSuccessors> succ_bitmap = GetAllowedSuccessors(cur_block);
194 // Get next successor allowed.
195 while (++(*cur_succ) < static_cast<ssize_t>(kMaxFilterableSuccessors) &&
196 !succ_bitmap.test(*cur_succ)) {
197 DCHECK_GE(*cur_succ, 0);
198 }
199 if (*cur_succ >= static_cast<ssize_t>(cur_block->GetSuccessors().size())) {
200 // No more successors. Mark that we've checked everything. Later visits
201 // to this node can use the existing data.
202 DCHECK_LE(*cur_succ, static_cast<ssize_t>(kMaxFilterableSuccessors));
203 *cur_succ = kMaxFilterableSuccessors;
204 pop_block();
205 continue;
206 }
207 const HBasicBlock* nxt = cur_block->GetSuccessors()[*cur_succ];
208 DCHECK(nxt != nullptr) << "id: " << *cur_succ
209 << " max: " << cur_block->GetSuccessors().size();
210 if (visiting.IsBitSet(nxt->GetBlockId())) {
211 // This is a loop. Mark it and continue on. Mark allowed-successor on
212 // this block's results as well.
213 result.set(*cur_succ);
214 propagate_true();
215 } else {
216 // Not a loop yet. Recur.
217 push_block(nxt);
218 }
219 }
220 }
221 // If we can't reach the end then there is no path through the graph without
222 // hitting excluded blocks
223 if (UNLIKELY(!start_reaches_end)) {
224 valid_ = false;
225 return;
226 }
227 // Mark blocks we didn't see in the ReachesEnd flood-fill
228 for (const HBasicBlock* blk : graph_->GetBlocks()) {
229 if (blk != nullptr &&
230 results[blk->GetBlockId()].none() &&
231 blk != graph_->GetExitBlock() &&
232 blk != graph_->GetEntryBlock()) {
233 // We never visited this block, must be unreachable.
234 unreachable_blocks_.SetBit(blk->GetBlockId());
235 }
236 }
237 // write the new data.
238 memcpy(allowed_successors_.data(),
239 results.data(),
240 results.size() * sizeof(std::bitset<kMaxFilterableSuccessors>));
241 }
242 RecalculateExcludedCohort();
243}
244
245void ExecutionSubgraph::RemoveConcavity() {
246 if (UNLIKELY(!valid_)) {
247 return;
248 }
249 DCHECK(!needs_prune_);
250 for (const HBasicBlock* blk : graph_->GetBlocks()) {
251 if (blk == nullptr || unreachable_blocks_.IsBitSet(blk->GetBlockId())) {
252 continue;
253 }
254 uint32_t blkid = blk->GetBlockId();
255 if (std::any_of(unreachable_blocks_.Indexes().begin(),
256 unreachable_blocks_.Indexes().end(),
257 [&](uint32_t skipped) { return graph_->PathBetween(skipped, blkid); }) &&
258 std::any_of(unreachable_blocks_.Indexes().begin(),
259 unreachable_blocks_.Indexes().end(),
260 [&](uint32_t skipped) { return graph_->PathBetween(blkid, skipped); })) {
261 RemoveBlock(blk);
262 }
263 }
264 Prune();
265}
266
267void ExecutionSubgraph::RecalculateExcludedCohort() {
268 DCHECK(!needs_prune_);
269 excluded_list_.emplace(allocator_->Adapter(kArenaAllocLSA));
270 ScopedArenaVector<ExcludedCohort>& res = excluded_list_.value();
271 // Make a copy of unreachable_blocks_;
272 ArenaBitVector unreachable(allocator_, graph_->GetBlocks().size(), false, kArenaAllocLSA);
273 unreachable.Copy(&unreachable_blocks_);
274 // Split cohorts with union-find
275 while (unreachable.IsAnyBitSet()) {
276 res.emplace_back(allocator_, graph_);
277 ExcludedCohort& cohort = res.back();
278 // We don't allocate except for the queue beyond here so create another arena to save memory.
279 ScopedArenaAllocator alloc(graph_->GetArenaStack());
280 ScopedArenaQueue<const HBasicBlock*> worklist(alloc.Adapter(kArenaAllocLSA));
281 // Select an arbitrary node
282 const HBasicBlock* first = graph_->GetBlocks()[unreachable.GetHighestBitSet()];
283 worklist.push(first);
284 do {
285 // Flood-fill both forwards and backwards.
286 const HBasicBlock* cur = worklist.front();
287 worklist.pop();
288 if (!unreachable.IsBitSet(cur->GetBlockId())) {
289 // Already visited or reachable somewhere else.
290 continue;
291 }
292 unreachable.ClearBit(cur->GetBlockId());
293 cohort.blocks_.SetBit(cur->GetBlockId());
294 // don't bother filtering here, it's done next go-around
295 for (const HBasicBlock* pred : cur->GetPredecessors()) {
296 worklist.push(pred);
297 }
298 for (const HBasicBlock* succ : cur->GetSuccessors()) {
299 worklist.push(succ);
300 }
301 } while (!worklist.empty());
302 }
303 // Figure out entry & exit nodes.
304 for (ExcludedCohort& cohort : res) {
305 DCHECK(cohort.blocks_.IsAnyBitSet());
306 auto is_external = [&](const HBasicBlock* ext) -> bool {
307 return !cohort.blocks_.IsBitSet(ext->GetBlockId());
308 };
309 for (const HBasicBlock* blk : cohort.Blocks()) {
310 const auto& preds = blk->GetPredecessors();
311 const auto& succs = blk->GetSuccessors();
312 if (std::any_of(preds.cbegin(), preds.cend(), is_external)) {
313 cohort.entry_blocks_.SetBit(blk->GetBlockId());
314 }
315 if (std::any_of(succs.cbegin(), succs.cend(), is_external)) {
316 cohort.exit_blocks_.SetBit(blk->GetBlockId());
317 }
318 }
319 }
320}
321
322std::ostream& operator<<(std::ostream& os, const ExecutionSubgraph::ExcludedCohort& ex) {
323 ex.Dump(os);
324 return os;
325}
326
327void ExecutionSubgraph::ExcludedCohort::Dump(std::ostream& os) const {
328 auto dump = [&](BitVecBlockRange arr) {
329 os << "[";
330 bool first = true;
331 for (const HBasicBlock* b : arr) {
332 if (!first) {
333 os << ", ";
334 }
335 first = false;
336 os << b->GetBlockId();
337 }
338 os << "]";
339 };
340 auto dump_blocks = [&]() {
341 os << "[";
342 bool first = true;
343 for (const HBasicBlock* b : Blocks()) {
344 if (!entry_blocks_.IsBitSet(b->GetBlockId()) && !exit_blocks_.IsBitSet(b->GetBlockId())) {
345 if (!first) {
346 os << ", ";
347 }
348 first = false;
349 os << b->GetBlockId();
350 }
351 }
352 os << "]";
353 };
354
355 os << "{ entry: ";
356 dump(EntryBlocks());
357 os << ", interior: ";
358 dump_blocks();
359 os << ", exit: ";
360 dump(ExitBlocks());
361 os << "}";
362}
363
364} // namespace art