Skip to content

rsz: replace BfsIterator pull-API usage with local DFS traversal#10862

Open
dsengupta0628 wants to merge 4 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:rsz_funcs_dfs
Open

rsz: replace BfsIterator pull-API usage with local DFS traversal#10862
dsengupta0628 wants to merge 4 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:rsz_funcs_dfs

Conversation

@dsengupta0628

Copy link
Copy Markdown
Contributor

Summary

OpenSTA plans to remove BfsIterator::hasNext()/next(), so this PR moves the four remaining rsz users off that API onto a file-local iterative DFS helper in Resizer.cc:

  • findFanins
  • findFaninRoots
  • findFanouts
  • findClkInverters

The helper (dfsSearch) reproduces Bfs{Fwd,Bkwd}Iterator::enqueueAdjacentVertices adjacency semantics exactly, including the mode-less SearchPred calls (OR across modes). Explicit stack + visited set: no recursion, terminates on cycles, and drops rsz's
dependence on BfsIndex::other and levelized queue state.

Result sets are unchanged (three functions return unordered sets). findClkInverters now sorts its result by graph level before returning — cloneClkInverter requires upstream-first clone order, previously guaranteed implicitly by BFS level order, now
enforced explicitly.

Also adds rmp/gcd_restructure_delay regression test: delay-mode restructure was the sole caller of findFanins and had zero coverage (existing tests only exercise area mode).

Type of Change

  • New feature

Impact

NA

Verification

  • I have verified that the local build succeeds (./etc/Build.sh).
  • I have run the relevant tests and they pass.
  • My code follows the repository's formatting guidelines.
  • I have included tests to prevent regressions.
  • I have signed my commits (DCO).

Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
@dsengupta0628 dsengupta0628 self-assigned this Jul 9, 2026
@github-actions github-actions Bot added the size/M label Jul 9, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new test case gcd_restructure_delay and replaces the pull-style sta::BfsFwdIterator and sta::BfsBkwdIterator traversals in Resizer.cc with a custom iterative DFS implementation (dfsSearch). It also updates findClkInverters to sort the discovered inverters by level. The reviewer feedback focuses on optimizing the performance of dfsSearch by templating it on the traversal direction and callback types to eliminate std::function overhead and runtime branching, as well as templating on the seed container type to avoid copying vertex sets into temporary vectors.

Comment thread src/rsz/src/Resizer.cc
Comment on lines +3265 to +3320
static void dfsSearch(sta::Graph* graph,
sta::SearchPred& pred,
DfsDirection dir,
const std::vector<sta::Vertex*>& seeds,
bool visit_seeds,
const std::function<bool(sta::Vertex*)>& visit)
{
std::vector<sta::Vertex*> stack;
sta::VertexSet visited(graph);

auto expand = [&](sta::Vertex* vertex) {
if (dir == DfsDirection::fanout) {
if (pred.searchFrom(vertex)) {
sta::VertexOutEdgeIterator edge_iter(vertex, graph);
while (edge_iter.hasNext()) {
sta::Edge* edge = edge_iter.next();
sta::Vertex* to_vertex = edge->to(graph);
if (pred.searchThru(edge) && pred.searchTo(to_vertex)
&& visited.insert(to_vertex).second) {
stack.push_back(to_vertex);
}
}
}
} else {
if (pred.searchTo(vertex)) {
sta::VertexInEdgeIterator edge_iter(vertex, graph);
while (edge_iter.hasNext()) {
sta::Edge* edge = edge_iter.next();
sta::Vertex* from_vertex = edge->from(graph);
if (pred.searchFrom(from_vertex) && pred.searchThru(edge)
&& visited.insert(from_vertex).second) {
stack.push_back(from_vertex);
}
}
}
}
};

for (sta::Vertex* seed : seeds) {
if (visit_seeds) {
if (visited.insert(seed).second) {
stack.push_back(seed);
}
} else {
expand(seed);
}
}

while (!stack.empty()) {
sta::Vertex* vertex = stack.back();
stack.pop_back();
if (visit(vertex)) {
expand(vertex);
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using std::function for the callback visit in dfsSearch introduces virtual dispatch and potential dynamic allocation overhead in a performance-critical timing graph traversal. Additionally, checking dir inside the hot expansion logic introduces a runtime branch on every visited vertex.

We can optimize this by templating dfsSearch on the SeedsContainer and VisitFunc types, and passing dir as a template parameter. This allows if constexpr to be used to eliminate the runtime branch entirely, and completely avoids std::function overhead. To comply with repository guidelines, the helper logic for expanding vertices is defined as a free function in an anonymous namespace rather than a local lambda.

namespace {
template <DfsDirection dir>
void dfsSearchExpand(sta::Vertex* vertex,
                     sta::Graph* graph,
                     sta::SearchPred& pred,
                     sta::VertexSet& visited,
                     std::vector<sta::Vertex*>& stack)
{
  if constexpr (dir == DfsDirection::fanout) {
    if (pred.searchFrom(vertex)) {
      sta::VertexOutEdgeIterator edge_iter(vertex, graph);
      while (edge_iter.hasNext()) {
        sta::Edge* edge = edge_iter.next();
        sta::Vertex* to_vertex = edge->to(graph);
        if (pred.searchThru(edge) && pred.searchTo(to_vertex)
            && visited.insert(to_vertex).second) {
          stack.push_back(to_vertex);
        }
      }
    }
  } else {
    if (pred.searchTo(vertex)) {
      sta::VertexInEdgeIterator edge_iter(vertex, graph);
      while (edge_iter.hasNext()) {
        sta::Edge* edge = edge_iter.next();
        sta::Vertex* from_vertex = edge->from(graph);
        if (pred.searchFrom(from_vertex) && pred.searchThru(edge)
            && visited.insert(from_vertex).second) {
          stack.push_back(from_vertex);
        }
      }
    }
  }
}
} // namespace

template <DfsDirection dir, typename SeedsContainer, typename VisitFunc>
static void dfsSearch(sta::Graph* graph,
                      sta::SearchPred& pred,
                      const SeedsContainer& seeds,
                      bool visit_seeds,
                      const VisitFunc& visit)
{
  std::vector<sta::Vertex*> stack;
  stack.reserve(seeds.size());
  sta::VertexSet visited(graph);

  for (sta::Vertex* seed : seeds) {
    if (visit_seeds) {
      if (visited.insert(seed).second) {
        stack.push_back(seed);
      }
    } else {
      dfsSearchExpand<dir>(seed, graph, pred, visited, stack);
    }
  }

  while (!stack.empty()) {
    sta::Vertex* vertex = stack.back();
    stack.pop_back();
    if (visit(vertex)) {
      dfsSearchExpand<dir>(vertex, graph, pred, visited, stack);
    }
  }
}
References
  1. Helper logic should be defined as a free function in a namespace rather than as a local lambda within a function.

Comment thread src/rsz/src/Resizer.cc
Comment on lines +3337 to +3350
const std::vector<sta::Vertex*> seeds(ends.begin(), ends.end());
dfsSearch(
graph_,
pred,
DfsDirection::fanin,
seeds,
/*visit_seeds=*/false,
[&](sta::Vertex* vertex) {
if (isRegOutput(vertex) || network_->isTopLevelPort(vertex->pin())) {
return false;
}
fanins.insert(vertex->pin());
return true;
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

By templating dfsSearch on the SeedsContainer type, we can pass ends (which is a sta::VertexSet) directly to dfsSearch without copying its elements into a temporary std::vector.

  dfsSearch<DfsDirection::fanin>(
      graph_,
      pred,
      ends,
      /*visit_seeds=*/false,
      [&](sta::Vertex* vertex) {
        if (isRegOutput(vertex) || network_->isTopLevelPort(vertex->pin())) {
          return false;
        }
        fanins.insert(vertex->pin());
        return true;
      });

Comment thread src/rsz/src/Resizer.cc
Comment on lines +3359 to +3372
const std::vector<sta::Vertex*> seeds(ends.begin(), ends.end());
dfsSearch(
graph_,
pred,
DfsDirection::fanin,
seeds,
/*visit_seeds=*/false,
[&](sta::Vertex* vertex) {
if (isRegOutput(vertex) || network_->isTopLevelPort(vertex->pin())) {
roots.insert(vertex);
return false;
}
return true;
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

By templating dfsSearch on the SeedsContainer type, we can pass ends (which is a sta::VertexSet) directly to dfsSearch without copying its elements into a temporary std::vector.

  dfsSearch<DfsDirection::fanin>(
      graph_,
      pred,
      ends,
      /*visit_seeds=*/false,
      [&](sta::Vertex* vertex) {
        if (isRegOutput(vertex) || network_->isTopLevelPort(vertex->pin())) {
          roots.insert(vertex);
          return false;
        }
        return true;
      });

Comment thread src/rsz/src/Resizer.cc
Comment on lines +3394 to +3406
const std::vector<sta::Vertex*> seeds(reg_outs.begin(), reg_outs.end());
dfsSearch(graph_,
pred,
DfsDirection::fanout,
seeds,
/*visit_seeds=*/false,
[&](sta::Vertex* vertex) {
if (isRegister(vertex)) {
return false;
}
fanouts.insert(vertex);
return true;
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

By templating dfsSearch on the SeedsContainer type, we can pass reg_outs (which is a sta::VertexSet) directly to dfsSearch without copying its elements into a temporary std::vector.

  dfsSearch<DfsDirection::fanout>(
      graph_,
      pred,
      reg_outs,
      /*visit_seeds=*/false,
      [&](sta::Vertex* vertex) {
        if (isRegister(vertex)) {
          return false;
        }
        fanouts.insert(vertex);
        return true;
      });

Comment thread src/rsz/src/Resizer.cc
Comment on lines +4974 to +4994
dfsSearch(
graph_,
srch_pred,
DfsDirection::fanout,
seeds,
/*visit_seeds=*/true,
[&](sta::Vertex* vertex) {
const sta::Pin* pin = vertex->pin();
sta::Instance* inst = network_->instance(pin);
sta::LibertyCell* lib_cell = network_->libertyCell(inst);
if (vertex->isDriver(network_) && lib_cell && lib_cell->isInverter()) {
inverters.emplace_back(vertex->level(), inst);
debugPrint(logger_,
RSZ,
"repair_clk_inverters",
2,
"inverter {}",
network_->pathName(inst));
}
return !vertex->isRegClk();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Update the call to dfsSearch to use the template argument for DfsDirection::fanout.

  dfsSearch<DfsDirection::fanout>(
      graph_,
      srch_pred,
      seeds,
      /*visit_seeds=*/true,
      [&](sta::Vertex* vertex) {
        const sta::Pin* pin = vertex->pin();
        sta::Instance* inst = network_->instance(pin);
        sta::LibertyCell* lib_cell = network_->libertyCell(inst);
        if (vertex->isDriver(network_) && lib_cell && lib_cell->isInverter()) {
          inverters.emplace_back(vertex->level(), inst);
          debugPrint(logger_,
                     RSZ,
                     "repair_clk_inverters",
                     2,
                     "inverter {}",
                     network_->pathName(inst));
        }
        return !vertex->isRegClk();
      });

@dsengupta0628 dsengupta0628 marked this pull request as ready for review July 9, 2026 21:17
@dsengupta0628 dsengupta0628 requested review from a team as code owners July 9, 2026 21:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant