crucible-0.7: src/Lang/Crucible/Analysis/Fixpoint/Components.hs
-----------------------------------------------------------------------
-- |
-- Module : Lang.Crucible.Analysis.Fixpoint.Components
-- Description : Compute weak topological ordering of CFG
-- Copyright : (c) Galois, Inc 2015
-- License : BSD3
-- Maintainer : Tristan Ravitch <tristan@galois.com>
-- Stability : provisional
--
-- Compute a weak topological ordering over a control flow graph using
-- Bourdoncle's algorithm (See Note [Bourdoncle Components]).
------------------------------------------------------------------------
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
module Lang.Crucible.Analysis.Fixpoint.Components (
weakTopologicalOrdering,
WTOComponent(..),
SCC(..),
-- * Special cases
cfgWeakTopologicalOrdering,
cfgSuccessors,
cfgStart
) where
import Control.Applicative
import Control.Monad ( when, void )
import qualified Control.Monad.State.Strict as St
import qualified Data.Foldable as F
import qualified Data.Map as M
import qualified Data.Traversable as T
import Prelude
import Data.Parameterized.Some (Some(Some))
import Lang.Crucible.CFG.Core (CFG, BlockID)
import qualified Lang.Crucible.CFG.Core as CFG
-- | Compute a weak topological ordering over a control flow graph.
--
-- Weak topological orderings provide an efficient iteration order for
-- chaotic iterations in abstract interpretation and dataflow analysis.
weakTopologicalOrdering :: (Ord n) => (n -> [n]) -> n -> [WTOComponent n]
weakTopologicalOrdering successors start =
wtoPartition (St.execState (runM (visit start)) s0)
where
s0 = WTOState { wtoSuccessors = successors
, wtoPartition = []
, wtoStack = []
, wtoLabelSrc = unlabeled
, wtoLabels = M.empty
}
data WTOComponent n = SCC (SCC n)
| Vertex n
deriving (Functor, F.Foldable, T.Traversable, Show)
data SCC n = SCCData { wtoHead :: n
, wtoComps :: [WTOComponent n]
}
deriving (Functor, F.Foldable, T.Traversable, Show)
-- | Useful for creating a first argument to 'weakTopologicalOrdering'. See
-- also 'cfgWeakTopologicalOrdering'.
cfgSuccessors ::
CFG ext blocks init ret ->
Some (BlockID blocks) -> [Some (BlockID blocks)]
cfgSuccessors cfg = \(Some bid) -> CFG.nextBlocks (CFG.getBlock bid bm) where
bm = CFG.cfgBlockMap cfg
-- | Useful for creating a second argument to 'weakTopologicalOrdering'. See
-- also 'cfgWeakTopologicalOrdering'.
cfgStart :: CFG ext blocks init ret -> Some (BlockID blocks)
cfgStart cfg = Some (CFG.cfgEntryBlockID cfg)
-- | Compute a weak topological order for the CFG.
cfgWeakTopologicalOrdering ::
CFG ext blocks init ret ->
[WTOComponent (Some (BlockID blocks))]
cfgWeakTopologicalOrdering cfg = weakTopologicalOrdering (cfgSuccessors cfg) (cfgStart cfg)
visit :: (Ord n) => n -> M n Label
visit v = do
push v
cn <- labelVertex v
(leastLabel, isLoop) <- visitSuccessors v cn
cn' <- lookupLabel v
-- We only create a component if this vertex is the head of its
-- strongly-connected component (i.e., its label is the same as the
-- minimum label in its SCC, returned from visitSuccessors). If so,
-- we make a new component (which may be a singleton if the vertex
-- is not in a loop).
when (cn' == leastLabel) $ do
markDone v
-- Note that we always have to pop, but we might only use the
-- result if there was a loop
pop >>= \case
Just elt ->
case isLoop of
False ->
-- If there is no loop, add a singleton vertex to the partition
addComponent (Vertex v)
True -> do
-- Otherwise, unwind the stack and add a full component
unwindStack elt v
makeComponent v
Nothing -> error "Pop attempted on empty stack (Components:visit)"
-- We return the least label in the strongly-connected component
-- containing this vertex, which is used if we have to unwind back
-- to the SCC head vertex.
return leastLabel
-- | Unwind the stack until we reach the target node @v@
unwindStack :: (Ord n)
=> n -- ^ Current top of the stack
-> n -- ^ Target element
-> M n ()
unwindStack elt v =
case elt /= v of
False -> return ()
True -> do
resetLabel elt
pop >>= \case
Just elt' -> unwindStack elt' v
Nothing -> error $ "Emptied stack without finding target element (Components:unwindStack)"
-- | Make a component with the given head element by visiting
-- everything in the SCC and recursively creating a new partition.
makeComponent :: (Ord n) => n -> M n ()
makeComponent v = do
ctx <- St.get
-- Do a recursive traversal with an empty partition
let ctx' = St.execState (runM (go (wtoSuccessors ctx))) (ctx { wtoPartition = [] })
-- Restore the old partition but with the updated context
St.put (ctx' { wtoPartition = wtoPartition ctx })
let cmp = SCC $ SCCData { wtoHead = v
, wtoComps = wtoPartition ctx'
}
addComponent cmp
where
go successors = F.forM_ (successors v) $ \s -> do
sl <- lookupLabel s
when (sl == unlabeled) $ do
void (visit s)
-- | Visit successors of a node and find:
--
-- 1) The minimum label number of any reachable (indirect) successor
-- and 2) If the node is in a loop
visitSuccessors :: (Ord n) => n -> Label -> M n (Label, Bool)
visitSuccessors v leastLabel0 = do
sucs <- St.gets wtoSuccessors
F.foldlM go (leastLabel0, False) (sucs v)
where
go acc@(leastLabel, _) successor = do
scn <- lookupLabel successor
minScn <- case scn == unlabeled of
True -> visit successor
False -> return scn
case minScn <= leastLabel of
True -> return (minScn, True)
False -> return acc
-- | Assign a label to a vertex.
--
-- This generates the next available label and assigns it to the
-- vertex. Note that labels effectively start at 1, since 0 is used
-- to denote unassigned. The actual labels are never exposed to
-- users, so that isn't a big deal.
labelVertex :: (Ord n) => n -> M n Label
labelVertex v = do
cn <- nextLabel <$> St.gets wtoLabelSrc
St.modify' $ \s -> s { wtoLabelSrc = cn
, wtoLabels = M.insert v cn (wtoLabels s)
}
return cn
-- | Look up the label of a vertex
lookupLabel :: (Ord n) => n -> M n Label
lookupLabel v = do
lbls <- St.gets wtoLabels
case M.lookup v lbls of
Nothing -> return unlabeled
Just l -> return l
-- | Mark a vertex as processed by setting its Label to maxBound
markDone :: (Ord n) => n -> M n ()
markDone v =
St.modify' $ \s -> s { wtoLabels = M.insert v maxLabel (wtoLabels s) }
-- | Reset a label on a vertex to the unlabeled state
resetLabel :: (Ord n) => n -> M n ()
resetLabel v =
St.modify' $ \s -> s { wtoLabels = M.insert v unlabeled (wtoLabels s) }
-- | Add a component to the current partition
addComponent :: WTOComponent n -> M n ()
addComponent c =
St.modify' $ \s -> s { wtoPartition = c : wtoPartition s }
push :: n -> M n ()
push n = St.modify' $ \s -> s { wtoStack = n : wtoStack s }
pop :: M n (Maybe n)
pop = do
stk <- St.gets wtoStack
case stk of
[] -> return Nothing
n : rest -> do
St.modify' $ \s -> s { wtoStack = rest }
return (Just n)
data WTOState n = WTOState { wtoSuccessors :: n -> [n]
-- ^ The successor relation for the control flow graph
, wtoPartition :: [WTOComponent n]
-- ^ The partition we are building up
, wtoStack :: [n]
-- ^ A stack of visited nodes
, wtoLabelSrc :: Label
, wtoLabels :: M.Map n Label
}
newtype M n a = M { runM :: St.State (WTOState n) a }
deriving (Functor, Monad, St.MonadState (WTOState n), Applicative)
newtype Label = Label Int
deriving (Eq, Ord, Show)
nextLabel :: Label -> Label
nextLabel (Label n) = Label (n + 1)
unlabeled :: Label
unlabeled = Label 0
maxLabel :: Label
maxLabel = Label maxBound
{- Note [Bourdoncle Components]
Bourdoncle components are a weak topological ordering of graph
components that inform a good ordering for chaotic iteration. The
components also provide a good set of locations to insert widening
operators for abstract interpretation. The formulation was proposed
by Francois Bourdoncle in the paper "Efficient chaotic iteration
strategies with widenings" [1].
[1] http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.89.8183&rep=rep1&type=pdf
The basic idea of Bourdoncle's algorithm is to compute the recursive
strongly-connected components of the control flow graph, sorted into
topological order. It is based on Tarjan's SCC algorithm, except that
it recursively looks for strongly-connected components in each SCC it
finds.
-}