| 1419 | } |
| 1420 | |
| 1421 | Status DeadnessAnalysisImpl::PopulateFrame(absl::Span<Node* const> topo, |
| 1422 | bool use_optimistic_mode, |
| 1423 | bool* success) { |
| 1424 | CHECK(use_optimistic_mode && success != nullptr || |
| 1425 | !use_optimistic_mode && success == nullptr); |
| 1426 | |
| 1427 | // This an abstract interpretation over the deadness propagation semantics of |
| 1428 | // the graph executor. |
| 1429 | // |
| 1430 | // We iterate over the graph twice, each time in a topological order. On the |
| 1431 | // first iteration merge nodes with backedges are mapped to symbolic |
| 1432 | // predicates. On the second iteration we use the predicates assigned to the |
| 1433 | // backedges in the previous iteration to infer a more precise predicate for |
| 1434 | // the backedge merge nodes and all the nodes that transitively use it. |
| 1435 | // |
| 1436 | // We don't track the output indices for should_revisit. Instead, putting a |
| 1437 | // node in `should_revisit` denotes that the deadness flowing out from any |
| 1438 | // output from said node may have changed. This is fine; only switches |
| 1439 | // propagate different deadness along different output edges, and since the |
| 1440 | // delta is solely due to the input *values* (and not input deadness), the |
| 1441 | // delta should not change in the second iteration. |
| 1442 | std::vector<bool> should_revisit; |
| 1443 | should_revisit.resize(graph_.num_node_ids()); |
| 1444 | for (Node* n : topo) { |
| 1445 | VLOG(4) << "Visiting " << n->name(); |
| 1446 | TF_RETURN_IF_ERROR( |
| 1447 | HandleNode(n, /*should_revisit=*/nullptr, use_optimistic_mode)); |
| 1448 | if (n->IsNextIteration()) { |
| 1449 | // If this is a backedge for a merge node then remember to reprocess the |
| 1450 | // merge the next time we run. |
| 1451 | for (const Edge* e : n->out_edges()) { |
| 1452 | if (e->dst()->IsMerge()) { |
| 1453 | should_revisit[e->dst()->id()] = true; |
| 1454 | } |
| 1455 | } |
| 1456 | } |
| 1457 | } |
| 1458 | |
| 1459 | for (Node* n : topo) { |
| 1460 | // The nodes added to should_revisit in the previous loop need to be |
| 1461 | // revisited now. Reprocesing these initial nodes may add *their* consumers |
| 1462 | // to should_revisit, and these newly added nodes will also be processed by |
| 1463 | // this very same loop. Since we're traversing the graph in topological |
| 1464 | // order (producers before consumers) and HandleNode(n) can only ever add |
| 1465 | // n's consumers to should_revisit, we won't "miss" an addition to |
| 1466 | // should_revisit. |
| 1467 | if (should_revisit[n->id()]) { |
| 1468 | VLOG(4) << "Revisiting " << n->name(); |
| 1469 | TF_RETURN_IF_ERROR(HandleNode(n, &should_revisit)); |
| 1470 | } |
| 1471 | } |
| 1472 | |
| 1473 | // Check if the optimistic analysis converges. Specifically, check whether |
| 1474 | // all the predicates of the merge nodes in the same frame are the same. If |
| 1475 | // yes, report success. If not, report failure and clear the assigned |
| 1476 | // predicates. |
| 1477 | if (use_optimistic_mode) { |
| 1478 | bool is_converged = true; |
nothing calls this directly
no test coverage detected