All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opalj.graphs.DominanceFrontiers.scala Maven / Gradle / Ivy

The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package graphs

import org.opalj.collection.immutable.Chain
import org.opalj.collection.immutable.IntArraySet
import org.opalj.collection.immutable.IntTrieSet
import org.opalj.collection.immutable.IntRefPair
import org.opalj.collection.mutable.FixedSizeBitSet
import org.opalj.collection.mutable.IntArrayStack

/**
 * Representation of the dominance frontiers.
 *
 * @author Michael Eichberg
 */
final class DominanceFrontiers private (
        private val dfs: Array[IntArraySet] // IMPROVE Consider using an IntTrieSet (if it doesn't work, document it!)
) extends ControlDependencies {

    def apply(n: Int): IntArraySet = df(n)

    def maxNode: Int = dfs.length - 1

    /**
     * Returns the nodes in the dominance frontier of the given node.
     */
    def df(n: Int): IntArraySet = {
        val df = dfs(n)
        if (df eq null)
            IntArraySet.empty
        else
            df
    }

    def transitiveDF(n: Int): IntTrieSet = {
        var transitiveDF = IntTrieSet.empty ++ this.df(n).iterator
        var nodesToVisit = transitiveDF - n
        while (nodesToVisit.nonEmpty) {
            val IntRefPair(nextN, newNodesToVisit) = nodesToVisit.headAndTail
            nodesToVisit = newNodesToVisit
            val nextDF = this.df(nextN)
            transitiveDF ++= nextDF.iterator
            nodesToVisit ++= nextDF.iterator.filter(_ != nextN)
        }
        transitiveDF
    }

    def dominanceFrontiers: IndexedSeq[IntArraySet] = dfs

    def xIsDirectlyControlDependentOn(x: Int): IntArraySet = df(x)

    def xIsControlDependentOn(x: Int)(f: Int ⇒ Unit): Unit = {
        val maxNodeId = maxNode

        // IMPROVE Evaluate if a typed chain or an Int(Array|Trie)Set is more efficient...
        val seen = FixedSizeBitSet.create(maxNodeId)
        val worklist = new IntArrayStack(Math.min(10, maxNodeId / 3))
        worklist.push(x)

        do {
            val x = worklist.pop()

            df(x) foreach { y ⇒
                if (!seen.contains(y)) {
                    seen += y
                    worklist.push(y)
                    f(y)
                }
            }
        } while (worklist.nonEmpty)
    }

    //
    //
    // DEBUGGING RELATED FUNCTIONALITY
    //
    //

    /**
     * Creates a dot graph which depicts the dominance frontiers.
     *
     * @param   isNodeValid A function that returns `true` if the given int value
     *          identifies a valid node. If the underlying graph is not a sparse
     *          graph; i.e., if every index in the range [0...maxNode] identifies
     *          a valid node, then the default function, which always returns `true`,
     *          can be used.
     */
    def toDot(isNodeValid: Int ⇒ Boolean = _ ⇒ true): String = {
        val g = Graph.empty[Int]
        dfs.zipWithIndex.foreach { e ⇒
            val (df, s /*index*/ ) = e
            if (isNodeValid(s)) {
                if (df == null) {
                    g += s
                } else {
                    df.foreach { t ⇒ g += (s, t) }
                }
            }
        }
        g.toDot(rankdir = "BT", ranksep = "0.3")
    }
}

/**
 * Factory to compute [[DominanceFrontiers]].
 *
 * @author Michael Eichberg
 */
object DominanceFrontiers {

    /**
     * Computes the dominance frontiers for each node of a graph G using the (post) dominator tree.
     *
     * @example
     * {{{
     * // A graph taken from the paper:
     * // Efficiently Computing Static Single Assignment Form and the Control Dependence Graph
     * val g = org.opalj.graphs.Graph.empty[Int] += (0 → 1) += (1 → 2) += (2 → 3) += (2 → 7) += (3 → 4) += (3->5) += (5->6) += (4->6) += (6->8) += (7->8)  += (8->9) += (9->10) += (9->11) += (10->11) += (11->9) += (11 -> 12) += (12 -> 13) += (12 ->2) += (0 -> 13)
     * val foreachSuccessor = (n: Int) ⇒ g.successors.getOrElse(n, List.empty).foreach _
     * val foreachPredecessor = (n: Int) ⇒ g.predecessors.getOrElse(n, List.empty).foreach _
     * val dt = org.opalj.graphs.DominatorTree(0, false, foreachSuccessor, foreachPredecessor, 13)
     * val isValidNode = (n : Int) => n>= 0 && n <= 13
     * org.opalj.io.writeAndOpen(dt.toDot(),"g",".dt.gv")
     * val df = org.opalj.graphs.DominanceFrontiers(dt,isValidNode)
     * org.opalj.io.writeAndOpen(df.toDot(),"g",".df.gv")
     *
     *
     * // A degenerated graph which consists of a single node that has a self-reference.
     * val g = org.opalj.graphs.Graph.empty[Int] += (0 → 0)
     * val foreachSuccessor = (n: Int) ⇒ g.successors.getOrElse(n, List.empty).foreach _
     * val foreachPredecessor = (n: Int) ⇒ g.predecessors.getOrElse(n, List.empty).foreach _
     * val dtf = org.opalj.graphs.DominatorTreeFactory(0, true, foreachSuccessor, foreachPredecessor, 0)
     * val isValidNode = (n : Int) => n == 0
     * org.opalj.io.writeAndOpen(dtf.dt.toDot(),"g",".dt.gv")
     * val df = org.opalj.graphs.DominanceFrontiers(dtf,isValidNode)
     * org.opalj.io.writeAndOpen(df.toDot(),"g",".df.gv")
     * }}}
     *
     * @param   dt The dominator tree of the specified (flow) graph. We provide basic support
     *          for augmented post dominator trees: [[PostDominatorTree]]; we in particular
     *          handle common cases related to additional exit nodes as created by the implented(!)
     *          post dominator tree computation algorithm.
     *          However, the following case:
     *          {{{
     *          while (true) {
     *               if (i < 0) {
     *                   i += 1000;
     *                   // Exit Piont 1
     *               } else {
     *                   i -= 100;
     *                   // Exit Point 2
     *               }
     *          }
     *          }}}
     *          is not yet supported; it would require a significant transformation of the
     *          computed PDT, which we currently do not perform.
     *          Basically, in the PDT we would need to make both bodies dependent on the
     *          artifical exit node of the loop to ensure that both bodies are control-dependent
     *          on the "if" node.
     *
     * @param   isValidNode A function that returns `true` if the given id represents a node of the
     *          underlying graph. If the underlying graph contains a single, new artificial start
     *          node then this node may or may not be reported as a valid node; this is not relevant
     *          for this algorithm.
     */
    def apply(dt: AbstractDominatorTree, isValidNode: Int ⇒ Boolean): DominanceFrontiers = {
        val startNode: Int = dt.startNode
        val foreachSuccessorOf = dt.foreachSuccessorOf
        val max = dt.maxNode + 1

        val potentialChildrenCount = 3
        val children = new Array[IntArrayStack](max)
        var i = 0
        while (i < max) {
            if (isValidNode(i) && i != startNode) {
                val d = dt.idom(i)
                val dChildren = children(d)
                if (dChildren eq null) {
                    val child = new IntArrayStack(potentialChildrenCount)
                    child.push(i)
                    children(d) = child
                } else {
                    dChildren.push(i)
                }
            }
            i += 1
        }

        var dfs /* dominanceFrontiers */ = new Array[IntArraySet](max)

        @inline def dfLocal(n: Int): IntArraySet = {
            var s = IntArraySet.empty
            try {
                foreachSuccessorOf(n) { y ⇒ if (dt.dom(y) != n) s = s + y }
            } catch {
                case t: Throwable ⇒
                    throw new Throwable(s"failed iterating over successors of node $n", t)
            }
            s
        }

        val inDFSOrder = new IntArrayStack(Math.max(max - 2, 2))
        var nodes: Chain[Int] = Chain.singleton[Int](startNode)
        while (nodes.nonEmpty) {
            val n = nodes.head
            nodes = nodes.tail
            val nChildren = children(n)
            if (nChildren ne null) {
                inDFSOrder.push(n)
                nChildren.foreach { nodes :&:= _ }
            } else {
                // we immediately compute the dfs_local information
                dfs(n) = dfLocal(n)
            }
        }

        inDFSOrder foreach { n ⇒
            val s = children(n).foldLeft(dfLocal(n)) { (s, c) ⇒
                dfs(c).foldLeft(s) { (s, w) ⇒
                    if (!dt.strictlyDominates(n, w)) {
                        s + w
                    } else
                        s
                }
            }
            dfs(n) = s
        }

        if (dt.isAugmented) {
            dt match {
                case pdt: PostDominatorTree ⇒
                    // let's filter the extra exit points; recall that we are
                    // non-termination _in_sensitive
                    dfs = dfs.map { e ⇒
                        if (e ne null)
                            e -- pdt.additionalExitNodes
                        else
                            e
                    }

                case _: DominatorTree ⇒
                //nothing special to do

                case dt ⇒
                    org.opalj.log.OPALLogger.warn(
                        "computing dominance frontier",
                        s"the augmentation of $dt is not understood and ignored"
                    )(org.opalj.log.GlobalLogContext)
            }
        }
        new DominanceFrontiers(dfs)
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy