g2-0.2.0.0: src/G2/Execution/HPC.hs
{-# Language FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving,
MultiParamTypeClasses, StandaloneDeriving #-}
-- | Provides infrastructure to track which HPC ticks have been encountered during symbolic execution.
module G2.Execution.HPC ( HpcT
, HpcM
, HpcTracker
, LengthNTrack
, hpcTracker
, hpcReducer
, lnt
, lengthNSubpathOrderer) where
import G2.Execution.Reducer
import G2.Language
import Control.Monad.Identity
import Control.Monad.IO.Class
import qualified Control.Monad.State.Lazy as SM
import qualified Data.HashSet as HS
import qualified Data.HashMap.Lazy as HM
import Data.Maybe
import Data.Monoid
import qualified Data.Text as T
import System.IO
import System.Clock
newtype HpcT m a = HpcT (SM.StateT (HS.HashSet (Int, T.Text)) m a) deriving (Functor, Applicative, Monad)
deriving instance Monad m => (SM.MonadState (HS.HashSet (Int, T.Text)) (HpcT m))
type HpcM a = HpcT Identity a
data HpcTracker = HPC {
hpc_ticks :: HS.HashSet (Int, T.Text) -- ^ The HPC ticks that execution has reached
, tick_count :: Maybe Int -- ^ Total number of HPC ticks that we could reach. See [HPC Total Tick Count]
, num_reached :: Int -- ^ Total number of HPC ticks currently reached by execution
, initial_time :: TimeSpec -- ^ The initial creation time of the HpcTracker
, times_reached :: [TimeSpec] -- ^ A list of times, where each time corresponds to a new tick being reached, in reverse order
}
-- [HPC Total Tick Count]
-- We want to know (as good an approximation as possible of) how many HPC ticks our execution
-- could potentially reach. Knowing this requires the state, after memory cleaning has occurred.
--
-- Memory cleaning happens after the Reducer has been created, so we want to delay computing the
-- total tick count until execution has begun. But we don't want to repeatedly recompute
-- the total tick count, because this requires scanning the entire state. So instead,
-- we track the total tick count as a `Maybe Int`, which is `Nothing` (if we have not yet computed
-- the total tick count) or `Just i`, where `i` is the total tick count.
-- | State used by `hpcReducer`.
hpcTracker :: MonadIO m => m HpcTracker
hpcTracker = do
ts <- liftIO $ getTime Monotonic
return $ HPC { hpc_ticks = HS.empty, tick_count = Nothing, num_reached = 0, initial_time = ts, times_reached = [] }
hpcInsert :: MonadIO m => Int -> T.Text -> HpcTracker -> m HpcTracker
hpcInsert i t hpc@(HPC { hpc_ticks = tr, num_reached = nr, times_reached = reached, initial_time = init_ts }) =
case HS.member (i, t) tr of
True -> return hpc
False -> do
ts <- liftIO $ getTime Monotonic
return $ hpc { hpc_ticks = HS.insert (i, t) tr, num_reached = nr + 1, times_reached = ts:reached }
totalTickCount :: SM.MonadState HpcTracker m => Maybe T.Text -> State t -> m Int
totalTickCount m s = do
hpc@(HPC { tick_count = ttc }) <- SM.get
case ttc of
Just i -> return i
Nothing -> do
let i = getSum $ evalASTs (countHPCTicks m) (expr_env s)
SM.put $ hpc { tick_count = Just i }
return i
-- | A reducer that tracks and prints the number of HPC ticks encountered during execution.
hpcReducer :: (MonadIO m, SM.MonadState HpcTracker m) =>
Maybe T.Text -- ^ A module to track tick count in
-> Reducer m () t
hpcReducer md = (mkSimpleReducer (const ()) logTick) { afterRed = after }
where
logTick _ s@(State {curr_expr = CurrExpr _ (Tick (HpcTick i tm) _)}) b
| Just tm == md = do
hpc <- SM.get
hpc'@(HPC { num_reached = nr }) <- hpcInsert i tm hpc
SM.put hpc'
hpc_tick_num <- totalTickCount md s
liftIO $ putStr ("\r" ++ show nr ++ " / " ++ show hpc_tick_num)
liftIO $ hFlush stdout
return (NoProgress, [(s, ())], b)
logTick _ s b = return (NoProgress, [(s, ())], b)
after = do
hpc <- SM.get
let init_ts = initial_time hpc
ts = times_reached hpc
liftIO $ putStrLn $ "\nTicks reached: " ++ show (num_reached hpc)
case ts of
[] -> liftIO $ putStrLn $ "Last tick reached: N/A"
(t:_) -> liftIO $ putStrLn $ "Last tick reached: " ++ showTS (t - init_ts)
showTS :: TimeSpec -> String
showTS (TimeSpec { sec = s, nsec = n }) = let str_n = show n in show s ++ "." ++ replicate (9 - length str_n) '0' ++ show n
countHPCTicks :: Maybe T.Text -> Expr -> Sum Int
countHPCTicks m (Tick (HpcTick _ m2) _) | m == Just m2 = Sum 1
countHPCTicks _ _ = 0
-------------------------------------------------------------------------------
-- Length N Subpaths
-------------------------------------------------------------------------------
newtype LengthNTrack = LNT { unLNT :: HM.HashMap [(Int, T.Text)] Int }
-- | State used by `lengthNSubpathOrderer`.
lnt :: LengthNTrack
lnt = LNT HM.empty
-- | Orders states based on length N subpaths.
-- Each time execution switches states, the state with the least common most
-- recently explored length N subpath is chosen.
--
-- Based on the paper:
-- Steering Symbolic Execution to Less Traveled Paths
-- You Li, Zhendong Su, Linzhang Wang, Xuandong Li
lengthNSubpathOrderer :: SM.MonadState LengthNTrack m =>
Int -- ^ N, the length of the subpaths to track
-> Orderer m [(Int, T.Text)] Int t
lengthNSubpathOrderer n = (mkSimpleOrderer initial order update) { stepOrderer = step }
where
initial _ = []
order p _ _ = do
LNT sp <- SM.get
return $ fromMaybe 0 (HM.lookup p sp)
update p _ _ = p
step p _ _ (State { curr_expr = CurrExpr _ (Tick (HpcTick i m) _) }) = do
let p' = take n $ (i, m):p
SM.modify (LNT . HM.insertWith (+) p' 1 . unLNT)
return p'
step p _ _ _ = return p