g2-0.2.0.0: src/G2/Liquid/LHReducers.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE UndecidableInstances #-}
module G2.Liquid.LHReducers ( lhRed
, allCallsRed
, higherOrderCallsRed
, redArbErrors
, nonRedAbstractReturnsRed
, lhAcceptIfViolatedHalter
, lhSWHNFHalter
, lhLimitByAcceptedOrderer
, lhLimitByAcceptedHalter
, lhAbsHalter
, lhMaxOutputsHalter
, LHTracker (..)
, lhStdTimerHalter
, lhTimerHalter
, abstractCallsNum
, minAbstractCalls
, lhReduce
, initialTrack) where
import G2.Execution.NormalForms
import G2.Execution.Reducer
import G2.Execution.Rules
import G2.Language
import qualified G2.Language.Stack as Stck
import qualified G2.Language.ExprEnv as E
import G2.Liquid.Annotations
import G2.Liquid.Conversion
import G2.Liquid.Helpers
import G2.Liquid.SpecialAsserts
import Control.Monad.IO.Class
import qualified Data.HashSet as S
import Data.List
import Data.List.Extra
import qualified Data.Map as M
import Data.Maybe
import Data.Monoid hiding ((<>))
import Data.Ord
import Data.Semigroup
import qualified Data.Text as T
import Data.Time.Clock
-- lhReduce
-- When reducing for LH, we change the rule for evaluating Var f.
-- Var f can potentially split into two states.
-- (a) One state is exactly the same as the current reduce function: we lookup
-- up the var, and set the curr expr to its definition.
-- (b) [StateB] The other var is created if the definition of f is of the form:
-- lam x_1 ... lam x_n . let x = f x_1 ... x_n in Assert (a x_1 ... x_n x) x
--
-- We introduce a new symbolic variable x_s, with the same type of s, and
-- set the curr expr to
-- let x = x_s in Assert (a x'_1 ... x'_n x_s) x_s
-- appropriately binding x'_i to x_i in the expression environment
--
-- This allows us to choose any value for the return type of the function.
-- In this rule, we also return a b, oldb `mappend` [(f, [x_1, ..., x_n], x)]
-- This is essentially abstracting away the function definition, leaving
-- only the information that LH also knows (that is, the information in the
-- refinment type.)
lhReduce :: Name -> State LHTracker -> Maybe (Rule, [State LHTracker])
lhReduce cfn s@(State { curr_expr = CurrExpr Evaluate (Tick (NamedLoc tn) e@(Assume (Just fc) _ _))
, track = tr@(LHTracker { abstract_calls = abs_c })
, exec_stack = stck})
| cfn == tn =
let
stck' = if arguments fc == [] then Stck.filter (\f -> f /= UpdateFrame (funcName fc)) stck else stck
in
Just ( RuleOther
, [s { curr_expr = CurrExpr Evaluate e
, track = tr { abstract_calls = fc:abs_c }
, exec_stack = stck' }])
| otherwise = Nothing
lhReduce _ _ = Nothing
-- Counts the maximal number of Vars with names in the ExprEnv
-- that could be evaluated along any one path in the function
initialTrack :: ExprEnv -> Expr -> Int
initialTrack eenv (Var (Id n _)) =
case E.lookup n eenv of
Just _ -> 1
Nothing -> 0
initialTrack eenv (App e e') = initialTrack eenv e + initialTrack eenv e'
initialTrack eenv (Lam _ _ e) = initialTrack eenv e
initialTrack eenv (Let b e) = initialTrack eenv e + (getSum $ evalContainedASTs (Sum . initialTrack eenv) b)
initialTrack eenv (Case e _ _ a) = initialTrack eenv e + (getMax $ evalContainedASTs (Max . initialTrack eenv) a)
initialTrack eenv (Cast e _) = initialTrack eenv e
initialTrack eenv (Assume _ _ e) = initialTrack eenv e
initialTrack eenv (Assert _ _ e) = initialTrack eenv e
initialTrack _ _ = 0
data LHTracker = LHTracker { abstract_calls :: [FuncCall]
, last_var :: Maybe Name
, annotations :: AnnotMap
, all_calls :: [FuncCall]
, higher_order_calls :: [FuncCall] } deriving (Eq, Show)
minAbstractCalls :: [State LHTracker] -> Int
minAbstractCalls xs =
minimum $ 10000000000:mapMaybe (\s -> case true_assert s of
True -> Just $ abstractCallsNum s
False -> Nothing ) xs
abstractCallsNum :: State LHTracker -> Int
abstractCallsNum = length . abstract_calls . track
instance Named LHTracker where
names (LHTracker {abstract_calls = abs_c, last_var = n, annotations = anns, all_calls = ac, higher_order_calls = hc}) =
names abs_c <> names n <> names anns <> names ac <> names hc
rename old new (LHTracker {abstract_calls = abs_c, last_var = n, annotations = anns, all_calls = ac, higher_order_calls = hc}) =
LHTracker { abstract_calls = rename old new abs_c
, last_var = rename old new n
, annotations = rename old new anns
, all_calls = rename old new ac
, higher_order_calls = rename old new hc }
renames hm (LHTracker {abstract_calls = abs_c, last_var = n, annotations = anns, all_calls = ac, higher_order_calls = hc}) =
LHTracker { abstract_calls = renames hm abs_c
, last_var = renames hm n
, annotations = renames hm anns
, all_calls = renames hm ac
, higher_order_calls = renames hm hc }
instance ASTContainer LHTracker Expr where
containedASTs (LHTracker {abstract_calls = abs_c, annotations = anns, all_calls = ac, higher_order_calls = hc}) =
containedASTs abs_c ++ containedASTs anns ++ containedASTs ac ++ containedASTs hc
modifyContainedASTs f lht@(LHTracker {abstract_calls = abs_c, annotations = anns, all_calls = ac, higher_order_calls = hc}) =
lht { abstract_calls = modifyContainedASTs f abs_c
, annotations = modifyContainedASTs f anns
, all_calls = modifyContainedASTs f ac
, higher_order_calls = modifyContainedASTs f hc}
instance ASTContainer LHTracker Type where
containedASTs (LHTracker {abstract_calls = abs_c, annotations = anns, all_calls = ac, higher_order_calls = hc}) =
containedASTs abs_c ++ containedASTs anns ++ containedASTs ac ++ containedASTs hc
modifyContainedASTs f lht@(LHTracker {abstract_calls = abs_c, annotations = anns, all_calls = ac, higher_order_calls = hc}) =
lht {abstract_calls = modifyContainedASTs f abs_c
, annotations = modifyContainedASTs f anns
, all_calls = modifyContainedASTs f ac
, higher_order_calls = modifyContainedASTs f hc }
{-# INLINE lhRed #-}
lhRed :: Monad m => Name -> Reducer m () LHTracker
lhRed cfn = mkSimpleReducer (const ()) rr
where
rr _ s b = do
case lhReduce cfn s of
Just (_, s') ->
return $ ( InProgress
, zip s' (repeat ()), b)
Nothing -> return (Finished, [(s, ())], b)
{-# INLINE allCallsRed #-}
allCallsRed :: Monad m => Reducer m () LHTracker
allCallsRed = mkSimpleReducer (const ()) rr
where
rr _ s@(State { curr_expr = CurrExpr Evaluate (Assert (Just fc) _ _) }) b =
let
lht = (track s) { all_calls = fc:all_calls (track s) }
in
return $ (Finished, [(s { track = lht } , ())], b)
rr _ s b = return $ (Finished, [(s, ())], b)
{-# INLINE higherOrderCallsRed #-}
higherOrderCallsRed :: Monad m => Reducer m () LHTracker
higherOrderCallsRed = mkSimpleReducer (const ()) rr
where
rr _ s@(State { curr_expr = CurrExpr Evaluate (Tick (NamedLoc nl) (Assume (Just fc) _ _)) }) b | nl == higherOrderTickName=
let
lht = (track s) { higher_order_calls = fc:higher_order_calls (track s) }
in
return $ (Finished, [(s { track = lht } , ())], b)
rr _ s@(State { curr_expr = CurrExpr Evaluate (Tick (NamedLoc nl) (Assume (Just fc) _ real_assert@(Assert _ _ _))) }) b | nl == higherOrderTickName=
let
lht = (track s) { higher_order_calls = fc:higher_order_calls (track s) }
in
return $ (Finished, [(s { curr_expr = CurrExpr Evaluate real_assert
, track = lht } , ())], b)
rr _ s b = return $ (Finished, [(s, ())], b)
{-# INLINE redArbErrors #-}
redArbErrors :: Monad m => Reducer m () t
redArbErrors = mkSimpleReducer (const ()) rr
where
rr _ s@(State { curr_expr = CurrExpr er (Tick tick (Let [(_, Type t)] _)) }) b
| tick == arbErrorTickish =
let
(arb, _) = arbValue t (type_env s) (arb_value_gen b)
in
return (InProgress, [(s { curr_expr = CurrExpr er arb }, ())], b)
rr _ s b = return (Finished, [(s, ())], b)
-- LHLimitByAcceptedHalter should always be used
-- with LHLimitByAcceptedOrderer.
-- LHLimitByAcceptedHalter is parameterized by a cutoff, `c`.
-- It allows execution of a state only if
-- (1) No counterexamples have been found
-- (2) The earliest the best (fewest abstracted functions) counterexample
-- was found was at reduction step n, and the state has taken fewer
-- than c + n steps
-- If either of these is violated, we switch to a new state.
-- However, if we find a better (fewer abstracted functions) counterexamples
-- with a higher, n, we want to be able to go back to that state.
--
-- For this reason, we rely on discardOnStart to discard states that have taken
-- too many steps. Because the Orderer always chooses the State that has taken the
-- least steps, we only restart a State with too many steps once EVERY state has too
-- many steps.
-- | Halt if we go `n` steps past another, already accepted state
lhLimitByAcceptedHalter :: Monad m => Int -> Halter m (Maybe Int) LHTracker
lhLimitByAcceptedHalter co =
(mkSimpleHalter (const Nothing) update stop (\hv _ _ _ -> hv)) { discardOnStart = discard }
where
-- If we start trying to execute a state with more than the maximal number
-- of rules applied, we throw it away.
discard (Just v) _ s = num_steps s > v + co
discard Nothing _ _ = False
-- Find all accepted states with the (current) minimal number of abstracted functions
-- Then, get the minimal number of steps taken by one of those states
update _ (Processed { accepted = []}) _ = Nothing
update _ (Processed { accepted = acc@(_:_)}) _ =
Just . minimum . map num_steps
$ allMin (length . abstract_calls . track) acc
stop Nothing _ _ = return Continue
stop (Just nAcc) _ s =
return $ if num_steps s > nAcc + co then Switch else Continue
-- | Runs the state that had the fewest number of rules applied.
lhLimitByAcceptedOrderer :: Monad m => Orderer m () Int t
lhLimitByAcceptedOrderer = mkSimpleOrderer (const ()) (\_ _ -> return . num_steps ) (\_ _ -> return ())
allMin :: Ord b => (a -> b) -> [a] -> [a]
allMin f xs =
let
minT = minimum $ map f xs
in
filter (\s -> minT == (f s)) xs
-- | Halt if we abstract more calls than some other already accepted state
{-# INLINE lhAbsHalter #-}
lhAbsHalter :: Monad m => T.Text -> Maybe T.Text -> ExprEnv -> Halter m Int LHTracker
lhAbsHalter entry modn eenv = mkSimpleHalter initial update stop step
where
-- We initialize the maximal number of abstracted variables,
-- to the number of variables in the entry function
initial _ =
let
fe = case E.occLookup entry modn eenv of
Just e -> e
Nothing -> error $ "initOrder: Bad function passed\n" ++ show entry ++ " " ++ show modn
in
initialTrack eenv fe
update ii (Processed {accepted = acc}) _ =
minimum $ ii:mapMaybe (\s -> case true_assert s of
True -> Just . length . abstract_calls . track $ s
False -> Nothing) acc
stop hv _ s =
return $ if length (abstract_calls $ track s) > hv
then Discard
else Continue
step hv _ _ _ = hv
{-# INLINE lhMaxOutputsHalter #-}
lhMaxOutputsHalter :: Monad m => Int -> Halter m Int LHTracker
lhMaxOutputsHalter mx = (mkSimpleHalter
(const mx)
(\hv _ _ -> hv)
(\_ _ _ -> return Continue)
(\hv _ _ _ -> hv)) { discardOnStart = discard}
where
discard m (Processed { accepted = acc }) _ = length acc' >= m
where
min_abs = minAbstractCalls acc
acc' = filter (\s -> abstractCallsNum s == min_abs) acc
{-# INLINE lhStdTimerHalter #-}
lhStdTimerHalter :: (MonadIO m, MonadIO m_run) => NominalDiffTime -> m (Halter m_run Int t)
lhStdTimerHalter ms = lhTimerHalter ms 10
{-# INLINE lhTimerHalter #-}
lhTimerHalter :: (MonadIO m, MonadIO m_run) => NominalDiffTime -> Int -> m (Halter m_run Int t)
lhTimerHalter ms ce = do
curr <- liftIO $ getCurrentTime
return $ mkSimpleHalter (const 0)
(\_ _ _ -> 0)
(stop curr)
step
where
stop it v (Processed { accepted = acc }) _
| v == 0
, any true_assert acc = do
curr <- liftIO $ getCurrentTime
let t_diff = diffUTCTime curr it
if t_diff > ms
then return Discard
else return Continue
| otherwise = return Continue
step v _ _ _
| v >= ce = 0
| otherwise = v + 1
-- | Reduces any non-SWHNF values being returned by an abstracted function
{-# INLINE nonRedAbstractReturnsRed #-}
nonRedAbstractReturnsRed :: Monad m => Reducer m () LHTracker
nonRedAbstractReturnsRed =
mkSimpleReducer (const ())
nonRedAbstractReturnsRedStep
nonRedAbstractReturnsRedStep :: Monad m => RedRules m () LHTracker
nonRedAbstractReturnsRedStep _
s@(State { expr_env = eenv
, curr_expr = cexpr
, exec_stack = stck
, track = LHTracker { abstract_calls = afs }
, true_assert = True })
b@(Bindings { deepseq_walkers = ds})
| Just af <- firstJust (absRetToRed eenv ds) afs = do
let stck' = Stck.push (CurrExprFrame NoAction cexpr) stck
cexpr' = CurrExpr Evaluate af
let s' = s { curr_expr = cexpr'
, exec_stack = stck'
}
return (InProgress, [(s', ())], b)
| otherwise = do
return (Finished, [(s, ())], b)
nonRedAbstractReturnsRedStep _ s b = return (Finished, [(s, ())], b)
absRetToRed :: ExprEnv -> Walkers -> FuncCall -> Maybe Expr
absRetToRed eenv ds (FuncCall { returns = r })
| not . normalForm eenv $ r
, Just strict_e <- mkStrict_maybe ds r =
Just $ fillLHDictArgs ds strict_e
| otherwise = Nothing
-- | Accepts a state when it is in SWHNF, true_assert is true,
-- and all abstracted functions have reduced returns.
-- Discards it if in SWHNF and true_assert is false
lhAcceptIfViolatedHalter :: Monad m => Halter m () LHTracker
lhAcceptIfViolatedHalter = mkSimpleHalter (const ()) (\_ _ _ -> ()) stop (\_ _ _ _ -> ())
where
stop _ _ s =
let
eenv = expr_env s
abs_calls = abstract_calls (track s)
in
case isExecValueForm s of
True
| true_assert s
, all (normalForm eenv . returns) abs_calls -> return Accept
| true_assert s -> return Continue
| otherwise -> return Discard
False -> return Continue
lhSWHNFHalter :: Monad m => Halter m () LHTracker
lhSWHNFHalter = mkSimpleHalter (const ()) (\_ _ _ -> ()) stop (\_ _ _ _ -> ())
where
stop _ _ s =
let
eenv = expr_env s
abs_calls = abstract_calls (track s)
in
case isExecValueForm s && all (normalForm eenv . returns) abs_calls of
True -> return Accept
False -> return Continue