packages feed

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