crucible-0.7: src/Lang/Crucible/Analysis/Reachable.hs
-----------------------------------------------------------------------
-- |
-- Module : Lang.Crucible.Analysis.Reachable
-- Description : Compute the reachable subgraph of a CFG
-- Copyright : (c) Galois, Inc 2015
-- License : BSD3
-- Maintainer : Joe Hendrix <jhendrix@galois.com>
-- Stability : provisional
--
-- Compute reachability on CFG blocks, reduce the CFG to include just
-- the reachable blocks, and remap block labels in the program to point
-- to the new, relabeled blocks.
------------------------------------------------------------------------
{-# LANGUAGE ScopedTypeVariables #-}
module Lang.Crucible.Analysis.Reachable
( reachableCFG
) where
import Control.Monad.Identity
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import qualified Data.Bimap as Bimap
import Data.Parameterized.Map (MapF)
import qualified Data.Parameterized.Map as MapF
import Data.Parameterized.TraversableFC
import qualified Data.Parameterized.Context as Ctx
import Lang.Crucible.CFG.Core
remapBlockID :: MapF (BlockID b) (BlockID b') -> BlockID b a -> BlockID b' a
remapBlockID m b =
fromMaybe (error $ "Could not remap block " ++ show b)
(MapF.lookup b m)
remapJumpTarget :: MapF (BlockID b) (BlockID b')
-> JumpTarget b c -> JumpTarget b' c
remapJumpTarget m (JumpTarget x r a) = JumpTarget (remapBlockID m x) r a
remapSwitchTarget :: MapF (BlockID b) (BlockID b')
-> SwitchTarget b c r -> SwitchTarget b' c r
remapSwitchTarget m (SwitchTarget x r a) = SwitchTarget (remapBlockID m x) r a
remapTermStmt :: MapF (BlockID b) (BlockID b') -> TermStmt b ret c -> TermStmt b' ret c
remapTermStmt m ts =
case ts of
Jump jmp -> Jump (remapJumpTarget m jmp)
Br c x y -> Br c (remapJumpTarget m x) (remapJumpTarget m y)
MaybeBranch tp r x y -> MaybeBranch tp r (remapSwitchTarget m x) (remapJumpTarget m y)
VariantElim c r a -> VariantElim c r (fmapFC (remapSwitchTarget m) a)
Return r -> Return r
TailCall f c a -> TailCall f c a
ErrorStmt r -> ErrorStmt r
remapBlock :: MapF (BlockID b) (BlockID b')
-> BlockID b' ctx
-> Block ext b r ctx
-> Block ext b' r ctx
remapBlock m nm b =
Block { blockID = nm
, blockInputs = blockInputs b
, _blockStmts =
runIdentity $
stmtSeqTermStmt
(\(l,s) -> Identity (TermStmt l (remapTermStmt m s)))
(_blockStmts b)
}
mkOldMap :: forall ext b b' r
. Ctx.Assignment (Block ext b r) b'
-> MapF (BlockID b) (BlockID b')
mkOldMap a = Ctx.forIndex (Ctx.size a) f MapF.empty
where f :: MapF (BlockID b) (BlockID b')
-> Ctx.Index b' c
-> MapF (BlockID b) (BlockID b')
f m new_index = MapF.insert (blockID b) (BlockID new_index) m
where b = a Ctx.! new_index
remapBlockMap :: forall ext b b' ret
. MapF (BlockID b) (BlockID b')
-> Ctx.Assignment (Block ext b ret) b'
-- ^ Map new blocks to old block IDs.
-> BlockMap ext b' ret
remapBlockMap oldToNew newToOld = Ctx.generate (Ctx.size newToOld) $ f
where f :: Ctx.Index b' ctx -> Block ext b' ret ctx
f i = remapBlock oldToNew (BlockID i) (newToOld Ctx.! i)
exploreReachable :: BlockMap ext blocks ret
-> BlockID blocks init
-> Map (Some (BlockID blocks)) Int
exploreReachable m d = exploreReachable' m [Some d] Map.empty
exploreReachable' :: BlockMap ext blocks ret
-> [Some (BlockID blocks)]
-> Map (Some (BlockID blocks)) Int
-> Map (Some (BlockID blocks)) Int
exploreReachable' _ [] r = r
exploreReachable' m (Some h:l) r =
case Map.lookup (Some h) r of
Just c -> exploreReachable' m l (Map.insert (Some h) (c+1) r)
Nothing -> do
let b = getBlock h m
exploreReachable' m (nextBlocks b ++ l) (Map.insert (Some h) 1 r)
insReachable :: BlockMap ext b r
-> Some (Ctx.Assignment (Block ext b r))
-> Some (BlockID b)
-> Some (Ctx.Assignment (Block ext b r))
insReachable m (Some a) (Some (BlockID block_id)) = Some $ a Ctx.:> (m Ctx.! block_id)
reachableCFG :: CFG ext blocks init ret -> SomeCFG ext init ret
reachableCFG g =
case foldl (insReachable old_map) (Some Ctx.empty) (Map.keys reachables) of
Some newToOld ->
-- trace ("Size change: " ++ show (Ctx.sizeInt (Ctx.size old_map) - Ctx.sizeInt (Ctx.size new_map))) $
SomeCFG g'
where oldToNew = mkOldMap newToOld
new_map = remapBlockMap oldToNew newToOld
new_breakpoints = Bimap.mapR (mapSome $ remapBlockID oldToNew) (cfgBreakpoints g)
g' = CFG { cfgHandle = cfgHandle g
, cfgBlockMap = new_map
, cfgEntryBlockID = remapBlockID oldToNew entry_id
, cfgBreakpoints = new_breakpoints
}
where old_map = cfgBlockMap g
entry_id = cfgEntryBlockID g
reachables = exploreReachable old_map entry_id