From 0583b06191ac0407dfd2b70d8f7014e3f4899078 Mon Sep 17 00:00:00 2001 From: karliss Date: Sat, 6 Apr 2019 22:37:49 +0300 Subject: [PATCH] Rewrite block sorting and placment so that unreachable blocks are processed. (#1428) * Rewrite block sorting and placment so that unreachable blocks are processed. * Use DFS instead of Kahn's algorithm for toposort as it makes it easier to deal with loops. * Remove unused code --- src/widgets/GraphGridLayout.cpp | 221 ++++++++++++++++---------------- src/widgets/GraphGridLayout.h | 10 +- 2 files changed, 120 insertions(+), 111 deletions(-) diff --git a/src/widgets/GraphGridLayout.cpp b/src/widgets/GraphGridLayout.cpp index 5766e1fd..55c61ed0 100644 --- a/src/widgets/GraphGridLayout.cpp +++ b/src/widgets/GraphGridLayout.cpp @@ -1,7 +1,9 @@ #include "GraphGridLayout.h" #include +#include #include +#include // Vector functions template @@ -16,96 +18,76 @@ GraphGridLayout::GraphGridLayout(GraphGridLayout::LayoutType layoutType) { } -std::vector GraphGridLayout::topoSort(LayoutState &state, unsigned long long entry) +std::vector GraphGridLayout::topoSort(LayoutState &state, ut64 entry) { auto &blocks = *state.blocks; - // Populate incoming lists + + // Run DFS to: + // * select backwards/loop edges + // * perform toposort + std::vector blockOrder; + // 0 - not visited + // 1 - in stack + // 2 - visited + std::unordered_map visited; + visited.reserve(state.blocks->size()); + std::stack> stack; + auto dfsFragment = [&visited, &blocks, &state, &stack, &blockOrder](ut64 first) { + visited[first] = 1; + stack.push({first, 0}); + while (!stack.empty()) { + auto v = stack.top().first; + auto edge_index = stack.top().second; + const auto &block = blocks[v]; + if (edge_index < block.edges.size()) { + ++stack.top().second; + auto target = block.edges[edge_index].target; + auto &targetState = visited[target]; + if (targetState == 0) { + targetState = 1; + stack.push({target, 0}); + state.grid_blocks[v].dag_edge.push_back(target); + } else if (targetState == 2) { + state.grid_blocks[v].dag_edge.push_back(target); + } // else { targetState == 1 in stack, loop edge } + } else { + stack.pop(); + visited[v] = 2; + blockOrder.push_back(v); + } + } + }; + + // Start with entry so that if start of function block is part of loop it + // is still kept at top unless it's impossible to do while maintaining + // topological order. + dfsFragment(entry); for (auto &blockIt : blocks) { - GraphBlock &block = blockIt.second; - for (auto &edge : block.edges) { - state.grid_blocks[edge.target].incoming.push_back(blockIt.first); + if (!visited[blockIt.first]) { + dfsFragment(blockIt.first); } } - std::unordered_set visited; - visited.insert(entry); - std::queue queue; - std::vector block_order; - queue.push(entry); - - bool changed = true; - while (changed) { - changed = false; - - // Pick nodes with single entrypoints - while (!queue.empty()) { - GraphBlock &block = blocks[queue.front()]; - queue.pop(); - block_order.push_back(block.entry); - for (const auto &edgeDescr : block.edges) { - ut64 edge = edgeDescr.target; - // Skip edge if we already visited it - if (visited.count(edge)) { - continue; - } - - // Some edges might not be available - if (!blocks.count(edge)) { - continue; - } - - // If this node has no other incoming edges, add it to the graph layout - if (state.grid_blocks[edge].incoming.size() == 1) { - removeFromVec(state.grid_blocks[edge].incoming, block.entry); - state.grid_blocks[block.entry].tree_edge.push_back(edge); - queue.push(blocks[edge].entry); - visited.insert(edge); - changed = true; - } else { - // Remove from incoming edges - removeFromVec(state.grid_blocks[edge].incoming, block.entry); - } - } - } - - // No more nodes satisfy constraints, pick a node to continue constructing the graph - ut64 best = 0; - int best_edges; - ut64 best_parent = 0; - for (auto &blockIt : blocks) { - GraphBlock &block = blockIt.second; - // Skip blocks we haven't visited yet - if (!visited.count(block.entry)) { - continue; - } - for (const auto &edgeDescr : block.edges) { - ut64 edge = edgeDescr.target; - // If we already visited the exit, skip it - if (visited.count(edge)) { - continue; - } - if (!blocks.count(edge)) { - continue; - } - // find best edge - if ((best == 0) || ((int)state.grid_blocks[edge].incoming.size() < best_edges) || ( - ((int)state.grid_blocks[edge].incoming.size() == best_edges) && (edge < best))) { - best = edge; - best_edges = state.grid_blocks[edge].incoming.size(); - best_parent = block.entry; - } - } - } - if (best != 0) { - auto &best_parentb = state.grid_blocks[best_parent]; - removeFromVec(state.grid_blocks[best].incoming, best_parent); - best_parentb.tree_edge.push_back(best); - visited.insert(best); - queue.push(best); - changed = true; + // assign levels and select tree edges + for (auto it = blockOrder.rbegin(), end = blockOrder.rend(); it != end; it++) { + auto &block = state.grid_blocks[*it]; + int nextLevel = block.level + 1; + for (auto target : block.dag_edge) { + auto &targetBlock = state.grid_blocks[target]; + targetBlock.level = std::max(targetBlock.level, nextLevel); } } - return block_order; + for (auto &blockIt : state.grid_blocks) { + auto &block = blockIt.second; + for (auto targetId : block.dag_edge) { + auto &targetBlock = state.grid_blocks[targetId]; + if (!targetBlock.has_parent && targetBlock.level == block.level + 1) { + block.tree_edge.push_back(targetId); + targetBlock.has_parent = true; + } + } + } + return blockOrder; } void GraphGridLayout::CalculateLayout(std::unordered_map &blocks, ut64 entry, @@ -121,23 +103,32 @@ void GraphGridLayout::CalculateLayout(std::unordered_map &bloc } auto block_order = topoSort(layoutState, entry); - computeBlockPlacement(entry, layoutState); + computeAllBlockPlacement(block_order, layoutState); for (auto &blockIt : blocks) { layoutState.edge[blockIt.first].resize(blockIt.second.edges.size()); } // Prepare edge routing - auto &entryb = layoutState.grid_blocks[entry]; + //auto &entryb = layoutState.grid_blocks[entry]; + int col_count = 1; + int row_count = 0; + for (const auto &blockIt : layoutState.grid_blocks) { + if (!blockIt.second.has_parent) { + row_count = std::max(row_count, blockIt.second.row_count); + col_count += blockIt.second.col_count; + } + } + row_count += 2; EdgesVector horiz_edges, vert_edges; - horiz_edges.resize(entryb.row_count + 1); - vert_edges.resize(entryb.row_count + 1); + horiz_edges.resize(row_count + 1); + vert_edges.resize(row_count + 1); Matrix edge_valid; - edge_valid.resize(entryb.row_count + 1); - for (int row = 0; row < entryb.row_count + 1; row++) { - horiz_edges[row].resize(entryb.col_count + 1); - vert_edges[row].resize(entryb.col_count + 1); - edge_valid[row].assign(entryb.col_count + 1, true); + edge_valid.resize(row_count + 1); + for (int row = 0; row < row_count + 1; row++) { + horiz_edges[row].resize(col_count + 1); + vert_edges[row].resize(col_count + 1); + edge_valid[row].assign(col_count + 1, true); } for (auto &blockIt : layoutState.grid_blocks) { @@ -158,10 +149,10 @@ void GraphGridLayout::CalculateLayout(std::unordered_map &bloc // Compute edge counts for each row and column std::vector col_edge_count, row_edge_count; - col_edge_count.assign(entryb.col_count + 1, 0); - row_edge_count.assign(entryb.row_count + 1, 0); - for (int row = 0; row < entryb.row_count + 1; row++) { - for (int col = 0; col < entryb.col_count + 1; col++) { + col_edge_count.assign(col_count + 1, 0); + row_edge_count.assign(row_count + 1, 0); + for (int row = 0; row < row_count + 1; row++) { + for (int col = 0; col < col_count + 1; col++) { if (int(horiz_edges[row][col].size()) > row_edge_count[row]) row_edge_count[row] = int(horiz_edges[row][col].size()); if (int(vert_edges[row][col].size()) > col_edge_count[col]) @@ -172,8 +163,8 @@ void GraphGridLayout::CalculateLayout(std::unordered_map &bloc //Compute row and column sizes std::vector col_width, row_height; - col_width.assign(entryb.col_count + 1, 0); - row_height.assign(entryb.row_count + 1, 0); + col_width.assign(col_count + 1, 0); + row_height.assign(row_count + 1, 0); for (auto &blockIt : blocks) { GraphBlock &block = blockIt.second; GridBlock &grid_block = layoutState.grid_blocks[blockIt.first]; @@ -187,19 +178,19 @@ void GraphGridLayout::CalculateLayout(std::unordered_map &bloc // Compute row and column positions std::vector col_x, row_y; - col_x.assign(entryb.col_count, 0); - row_y.assign(entryb.row_count, 0); - std::vector col_edge_x(entryb.col_count + 1); - std::vector row_edge_y(entryb.row_count + 1); + col_x.assign(col_count, 0); + row_y.assign(row_count, 0); + std::vector col_edge_x(col_count + 1); + std::vector row_edge_y(row_count + 1); int x = layoutConfig.block_horizontal_margin * 2; - for (int i = 0; i < entryb.col_count; i++) { + for (int i = 0; i < col_count; i++) { col_edge_x[i] = x; x += layoutConfig.block_horizontal_margin * col_edge_count[i]; col_x[i] = x; x += col_width[i]; } int y = layoutConfig.block_vertical_margin * 2; - for (int i = 0; i < entryb.row_count; i++) { + for (int i = 0; i < row_count; i++) { row_edge_y[i] = y; // TODO: The 1 when row_edge_count is 0 is not needed on the original.. not sure why it's required for us if (!row_edge_count[i]) { @@ -209,12 +200,12 @@ void GraphGridLayout::CalculateLayout(std::unordered_map &bloc row_y[i] = y; y += row_height[i]; } - col_edge_x[entryb.col_count] = x; - row_edge_y[entryb.row_count] = y; + col_edge_x[col_count] = x; + row_edge_y[row_count] = y; width = x + (layoutConfig.block_horizontal_margin * 2) + (layoutConfig.block_horizontal_margin * - col_edge_count[entryb.col_count]); + col_edge_count[col_count]); height = y + (layoutConfig.block_vertical_margin * 2) + (layoutConfig.block_vertical_margin * - row_edge_count[entryb.row_count]); + row_edge_count[row_count]); //Compute node positions for (auto &blockIt : blocks) { @@ -285,10 +276,23 @@ void GraphGridLayout::CalculateLayout(std::unordered_map &bloc } } +void GraphGridLayout::computeAllBlockPlacement(const std::vector &blockOrder, + LayoutState &layoutState) const +{ + for (auto blockId : blockOrder) { + computeBlockPlacement(blockId, layoutState); + } + int col = 0; + for (auto blockId : blockOrder) { + if (!layoutState.grid_blocks[blockId].has_parent) { + adjustGraphLayout(layoutState.grid_blocks[blockId], layoutState.grid_blocks, col, 1); + col += layoutState.grid_blocks[blockId].col_count; + } + } +} // Prepare graph // This computes the position and (row/col based) size of the block -// Recursively calls itself for each child of the GraphBlock void GraphGridLayout::computeBlockPlacement(ut64 blockId, LayoutState &layoutState) const { auto &block = layoutState.grid_blocks[blockId]; @@ -301,7 +305,6 @@ void GraphGridLayout::computeBlockPlacement(ut64 blockId, LayoutState &layoutSta for (size_t i = 0; i < block.tree_edge.size(); i++) { ut64 edge = block.tree_edge[i]; auto &edgeb = blocks[edge]; - computeBlockPlacement(edge, layoutState); row_count = std::max(edgeb.row_count + 1, row_count); childColumn = edgeb.col; } diff --git a/src/widgets/GraphGridLayout.h b/src/widgets/GraphGridLayout.h index 24fd30e5..9e6b1525 100644 --- a/src/widgets/GraphGridLayout.h +++ b/src/widgets/GraphGridLayout.h @@ -23,8 +23,10 @@ private: struct GridBlock { ut64 id; - std::vector incoming; std::vector tree_edge; // subset of outgoing edges that form a tree + std::vector dag_edge; // subset of outgoing edges that form a tree + std::size_t has_parent = false; + int level = 0; // Number of rows in block int row_count = 0; @@ -63,9 +65,13 @@ private: std::unordered_map> edge; }; + using GridBlockMap = std::unordered_map; + + void computeAllBlockPlacement(const std::vector &blockOrder, + LayoutState &layoutState) const; void computeBlockPlacement(ut64 blockId, LayoutState &layoutState) const; - void adjustGraphLayout(GridBlock &block, std::unordered_map &blocks, + void adjustGraphLayout(GridBlock &block, GridBlockMap &blocks, int col, int row) const; static std::vector topoSort(LayoutState &state, ut64 entry);