packages feed

g2-0.2.0.0: src/G2/Liquid/Inference/Config.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}

module G2.Liquid.Inference.Config (
                                    Progress (..)
                                  , Progresser (..)
                                  , ProgresserM (..)
                                  , MaxSize (..)
                                  , newProgress
                                  , runProgresser

                                  , InfConfigM (..)
                                  , InferenceConfig (..)
                                  , Configs (..)
                                  , InfConfig (..)
                                  , runConfigs

                                  , getAllConfigsForInf
                                  , mkInferenceConfig
                                  , mkInferenceConfigDirect
                                  , adjustConfig
                                  , adjustConfigPostLH

                                  , incrMaxSize ) where

import G2.Config.Config
import G2.Initialization.Types
import G2.Language ( ExprEnv
                   , Expr
                   , Name (..)
                   , Type (..))
import qualified G2.Language.ExprEnv as E
import qualified G2.Language.Support as S
import G2.Language.Typing
import G2.Liquid.Config
import G2.Liquid.Conversion
import G2.Liquid.Helpers
import G2.Liquid.TCValues
import G2.Liquid.Types
import G2.Liquid.Inference.PolyRef
import G2.Translation.Haskell

#if MIN_VERSION_liquidhaskell(0,8,10)
import G2.Liquid.Types (GhcInfo (..), GhcSpec (..))
#else
import Language.Haskell.Liquid.Types (GhcInfo (..), GhcSpec (..))
#endif
import qualified Language.Haskell.Liquid.Types as LH

#if MIN_VERSION_GLASGOW_HASKELL(9,0,2,0)
import GHC.Types.Var as V
#else
import Var as V
#endif

import Control.Monad.Reader
import Control.Monad.State.Lazy
import qualified Data.HashSet as S
import qualified Data.Map as M
import Data.Maybe (isJust)
import Data.Monoid ((<>))
import qualified Data.Text as T
import Data.Time.Clock
import Options.Applicative
import System.Directory


-------------------------------
-- Progresser
-------------------------------

newtype MaxSize = MaxSize Integer

data Progress =
    Progress { ex_max_ce :: M.Map (T.Text, Maybe T.Text) Int -- ^ Gives an extra budget for maximum ce number
             , ex_max_depth :: Int -- ^ Gives an extra budget for the depth limit
             , ex_max_time :: M.Map (T.Text, Maybe T.Text) NominalDiffTime -- ^ Gives an extra max bufget for time
             , max_synth_form_size :: MaxSize
             , max_synth_coeff_size :: MaxSize
             , synth_fresh :: Int -- ^ An int to increment to get fresh names
             }

newProgress :: Progress
newProgress = Progress { ex_max_ce = M.empty
                       , ex_max_depth = 0
                       , ex_max_time = M.empty
                       , max_synth_form_size = MaxSize 1
                       , max_synth_coeff_size = MaxSize 2
                       , synth_fresh = 0 }

class Progresser p where
    extraMaxCEx ::  (T.Text, Maybe T.Text) -> p -> Int
    incrMaxCEx :: (T.Text, Maybe T.Text) -> p -> p

    extraMaxDepth ::  p -> Int
    incrMaxDepth :: p -> p

    extraMaxTime :: (T.Text, Maybe T.Text) -> p -> NominalDiffTime
    incrMaxTime :: (T.Text, Maybe T.Text) -> p -> p

    maxSynthFormSize :: p -> MaxSize
    incrMaxSynthFormSize :: p -> p

    maxSynthCoeffSize :: p -> MaxSize
    incrMaxSynthCoeffSize :: p -> p
    setMaxSynthCoeffSize :: MaxSize -> p -> p

    synthFresh :: p -> Int
    incrSynthFresh :: p -> p

instance Progresser Progress where
    extraMaxCEx n (Progress { ex_max_ce = m }) = M.findWithDefault 0 n m
    incrMaxCEx n p@(Progress { ex_max_ce = m }) =
        p { ex_max_ce = M.insertWith (+) n 2 m }

    extraMaxDepth (Progress { ex_max_depth = m }) = m
    incrMaxDepth p@(Progress { ex_max_depth = m }) = p { ex_max_depth = m + 200 }

    extraMaxTime n (Progress { ex_max_time = m }) = M.findWithDefault 0 n m
    incrMaxTime n p@(Progress { ex_max_time = m }) =
        p { ex_max_time = M.insertWith (+) n 4 m }

    maxSynthFormSize (Progress { max_synth_form_size = mss }) = mss
    incrMaxSynthFormSize p@(Progress { max_synth_form_size = mss }) = p { max_synth_form_size = incrMaxSize 1 mss }

    maxSynthCoeffSize (Progress { max_synth_coeff_size = mss }) = mss
    incrMaxSynthCoeffSize p@(Progress { max_synth_coeff_size = mss }) = p { max_synth_coeff_size = incrMaxSize 2 mss }
    setMaxSynthCoeffSize max_size p = p { max_synth_coeff_size = max_size }


    synthFresh (Progress { synth_fresh = syf }) = syf
    incrSynthFresh p@(Progress { synth_fresh = syf }) = p { synth_fresh = syf + 1 }

class Monad m => ProgresserM m where
    extraMaxCExM :: (T.Text, Maybe T.Text) -> m Int
    incrMaxCExM :: (T.Text, Maybe T.Text) -> m ()

    extraMaxDepthM :: m Int
    incrMaxDepthM :: m ()

    extraMaxTimeM :: (T.Text, Maybe T.Text) -> m NominalDiffTime
    incrMaxTimeM :: (T.Text, Maybe T.Text) -> m ()

    maxSynthFormSizeM :: m MaxSize
    incrMaxSynthFormSizeM :: m ()

    maxSynthCoeffSizeM :: m MaxSize
    incrMaxSynthCoeffSizeM :: m ()
    setMaxSynthCoeffSizeM :: MaxSize -> m ()

    synthFreshM :: m Int
    incrSynthFreshM :: m ()

instance (Monad m, Progresser p) => ProgresserM (StateT p m) where
    extraMaxCExM n = gets (extraMaxCEx n)
    incrMaxCExM n = modify' (incrMaxCEx n)

    extraMaxDepthM = gets extraMaxDepth
    incrMaxDepthM = modify' incrMaxDepth

    extraMaxTimeM n = gets (extraMaxTime n)
    incrMaxTimeM n = modify' (incrMaxTime n)

    maxSynthFormSizeM = gets maxSynthFormSize
    incrMaxSynthFormSizeM = modify' incrMaxSynthFormSize

    maxSynthCoeffSizeM = gets maxSynthCoeffSize
    incrMaxSynthCoeffSizeM = modify' incrMaxSynthCoeffSize
    setMaxSynthCoeffSizeM max_size = modify' (setMaxSynthCoeffSize max_size)

    synthFreshM = gets synthFresh
    incrSynthFreshM = modify' incrSynthFresh

instance ProgresserM m => ProgresserM (ReaderT env m) where
    extraMaxCExM n = lift (extraMaxCExM n)
    incrMaxCExM n = lift (incrMaxCExM n)

    extraMaxDepthM = lift extraMaxDepthM
    incrMaxDepthM = lift incrMaxDepthM

    extraMaxTimeM n = lift (extraMaxTimeM n)
    incrMaxTimeM n = lift (incrMaxTimeM n)

    maxSynthFormSizeM = lift maxSynthFormSizeM
    incrMaxSynthFormSizeM = lift incrMaxSynthFormSizeM

    maxSynthCoeffSizeM = lift maxSynthCoeffSizeM
    incrMaxSynthCoeffSizeM = lift incrMaxSynthCoeffSizeM
    setMaxSynthCoeffSizeM max_size = lift (setMaxSynthCoeffSizeM max_size)

    synthFreshM = lift synthFreshM
    incrSynthFreshM = lift incrSynthFreshM

runProgresser :: (Monad m, Progresser p) => StateT p m a -> p -> m a
runProgresser = evalStateT

incrMaxSize :: Integer -> MaxSize -> MaxSize
incrMaxSize incr (MaxSize sz) = MaxSize (sz + incr)

-------------------------------
-- Configurations
-------------------------------

data Configs = Configs { g2_config :: Config
                       , g2lh_config :: LHConfig -- ^ Configurations for running G2 in LH mode
                       , lh_config :: LH.Config -- ^ LiquidHaskell configuration settings
                       , inf_config :: InferenceConfig }

class InfConfig c where
    g2Config :: c -> Config
    g2lhConfig :: c -> LHConfig
    lhConfig :: c -> LH.Config
    infConfig :: c -> InferenceConfig

class Monad m => InfConfigM m where
    g2ConfigM :: m Config
    g2lhConfigM :: m LHConfig
    lhConfigM :: m LH.Config
    infConfigM :: m InferenceConfig

instance InfConfig Configs where
    g2Config = g2_config
    g2lhConfig = g2lh_config
    lhConfig = lh_config
    infConfig = inf_config

instance InfConfig env => InfConfig (ReaderT env m a) where
    g2Config = return . g2Config =<< ask
    g2lhConfig = return . g2lhConfig =<< ask
    lhConfig = return . lhConfig =<< ask
    infConfig = return . infConfig =<< ask

instance (Monad m, InfConfig env) => InfConfigM (ReaderT env m) where 
    g2ConfigM = return . g2Config =<< ask 
    g2lhConfigM = return . g2lhConfig =<< ask
    lhConfigM = return . lhConfig =<< ask 
    infConfigM = return . infConfig =<< ask 

instance InfConfigM m => InfConfigM (StateT env m) where
    g2ConfigM = lift g2ConfigM
    g2lhConfigM = lift g2lhConfigM
    lhConfigM = lift lhConfigM
    infConfigM = lift infConfigM


data InferenceConfig =
    InferenceConfig { keep_quals :: Bool

                    , modules :: S.HashSet (Maybe T.Text)
                    , max_ce :: Int

                    , pre_refined :: S.HashSet  (T.Text, Maybe T.Text)
                    , refinable_funcs :: S.HashSet (T.Text, Maybe T.Text)

                    , restrict_coeffs :: Bool -- ^ If true, only allow coefficients in the range of -1 <= c <= 1

                    , use_extra_fcs :: Bool -- ^ If true, generate as many constraints as possible, if false, generate
                                            -- only those that are essential to block bad specifications 
                    , use_level_dec :: Bool
                    , use_negated_models :: Bool

                    , use_binary_minimization :: Bool -- ^ In parallel to the SMT's solvers minimization support,
                                                      --   use a binary search to minimize synthesis solutions

                    , use_invs :: Bool
                   
                    , timeout_se :: NominalDiffTime
                    , timeout_sygus :: NominalDiffTime }

getAllConfigsForInf :: IO (String, Bool, Config, LHConfig, InferenceConfig, Maybe String)
getAllConfigsForInf = do
    homedir <- getHomeDirectory
    execParser (mkAllConfigsForInf homedir)

mkAllConfigsForInf :: String -> ParserInfo (String, Bool, Config, LHConfig, InferenceConfig, Maybe String)
mkAllConfigsForInf homedir =
    info (((,,,,,) <$> getFileName
                <*> flag False True (long "count" <> help "count the number of functions in the file")
                <*> mkConfig homedir
                <*> mkLHConfig
                <*> mkInferenceConfig
                <*> mkFuncCheck) <**> helper)
          ( fullDesc
          <> progDesc "Automatically infers specifications to verify code using LiquidHaskell."
          <> header "The G2 Symbolic Execution Engine" )

getFileName :: Parser String
getFileName = argument str (metavar "FILE")

runConfigs :: InfConfig env => ReaderT env m a -> env -> m a
runConfigs = runReaderT

mkInferenceConfig :: Parser InferenceConfig
mkInferenceConfig = InferenceConfig
    <$> flag True False (long "keep-quals" <> help "allow qualifiers to be generated and used during inference")
    <*> pure S.empty
    <*> option auto (long "max-ce"
                   <> metavar "M"
                   <> value 5
                   <> help "the initial maximal number of counterexamples to return from any symbolic execution call")
    <*> pure S.empty
    <*> pure S.empty
    <*> switch (long "restrict-coeffs-1" <> help "restrict coefficients to -1, 0, 1")
    <*> flag True False (long "no-extra-fcs" <> help "do not generate extra (non-blocking) constraints")
    <*> flag True False (long "no-level-dec" <> help "do not use level descent")
    <*> flag True False (long "no-negated-models" <> help "do not use negated models")
    <*> flag True False (long "no-binary-min" <> help "use binary minimization during synthesis")
    <*> switch (long "use-invs" <> help "use invariant mode (benchmarking only)")
    <*> option (maybeReader (Just . fromInteger . read)) (long "timeout-se"
                   <> metavar "T"
                   <> value 5
                   <> help "timeout for symbolic execution")
    <*> option (maybeReader (Just . fromInteger . read)) (long "timeout-sygus"
                   <> metavar "T"
                   <> value 5
                   <> help "timeout for synthesis")

mkFuncCheck :: Parser (Maybe String)
mkFuncCheck =
    option (eitherReader (Right . Just))
            ( long "liquid-func"
            <> metavar "FUNC"
            <> value Nothing
            <> help "a function to directly run symbolic execution on")


mkInferenceConfigDirect :: [String] -> InferenceConfig
mkInferenceConfigDirect as =
    InferenceConfig { keep_quals = boolArg "keep-quals" as M.empty On
                    , modules = S.empty
                    , max_ce = strArg "max-ce" as M.empty read 5
                    , pre_refined = S.empty
                    , refinable_funcs = S.empty
                    , restrict_coeffs = boolArg "restrict-coeffs" as M.empty Off
                    , use_extra_fcs = boolArg "use-extra-fc" as M.empty On
                    , use_level_dec = boolArg "use-level-dec" as M.empty On
                    , use_negated_models = boolArg "use-negated-models" as M.empty On
                    , use_binary_minimization = True
                    , use_invs = boolArg "use-invs" as M.empty Off
                    , timeout_se = strArg "timeout-se" as M.empty (fromInteger . read) 5
                    , timeout_sygus = strArg "timeout-sygus" as M.empty (fromInteger . read) 10 }

adjustConfig :: Maybe T.Text -> SimpleState -> Config -> LHConfig -> InferenceConfig -> [GhcInfo] -> (Config, LHConfig, InferenceConfig)
adjustConfig main_mod (SimpleState { expr_env = eenv }) config lhconfig infconfig ghci =
    let
        -- ref = refinable main_mod meas tcv eenv

        pre = S.fromList
            . map (\(Name n m _ _) -> (n, m))
            . map (mkName . V.varName . fst)
            $ concatMap getTySigs ghci

        -- ns_mm = map (\(Name n m _ _) -> (n, m))
        --       -- . filter (\(Name n m _ _) -> not $ (n, m) `S.member` pre)
        --       -- . filter (\(Name n _ _ _) -> n `notElem` [ "mapReduce" ])
        --       -- . filter (\(Name n _ _ _) -> n `notElem` [ "mapReduce", "singleton", "concat", "append"
        --       --                                          , "map", "replicate", "empty", "zipWith", "add"])
        --       . filter (\(Name n m _ _) -> (n, m) `elem` ref)
        --       . E.keys
        --       . E.filter (not . tyVarNoMeas meas tcv)
        --       $ E.filter (not . tyVarRetTy) eenv

        ns_not_main = filter (\(n, _) -> n == "foldr1")
                    . map (\(Name n m _ _) -> (n, m))
                    . filter (\(Name _ m _ _) -> m /= main_mod)
                    $ E.keys eenv
    
        lhconfig' = lhconfig { only_top = True
                             , block_errors_in = S.fromList ns_not_main }

        infconfig' = infconfig { modules = S.singleton main_mod
                               , pre_refined = pre }
    in
    (config, lhconfig', infconfig')

adjustConfigPostLH :: Maybe T.Text -> Measures -> TCValues -> S.State t -> [GhcInfo] -> LHConfig -> LHConfig
adjustConfigPostLH main_mod meas tcv (S.State { S.expr_env = eenv, S.known_values = kv }) ghci lhconfig =
    let
        ref = refinable main_mod meas tcv ghci kv eenv
        
        ns_mm = map (\(Name n m _ _) -> (n, m))
              . filter (\(Name n m _ _) -> (n, m) `elem` ref)
              $ E.keys eenv

    in
    lhconfig { counterfactual = Counterfactual . CFOnly $ S.fromList ns_mm }

refinable :: Maybe T.Text -> Measures -> TCValues -> [GhcInfo] -> S.KnownValues -> ExprEnv -> [(T.Text, Maybe T.Text)]
refinable main_mod meas tcv ghci kv eenv = 
    let
        ns_mm = E.keys
              . E.filter (\e -> not (tyVarNoMeas meas tcv ghci e) || isPrimRetTy kv e)
              . E.filter (not . tyVarRetTy)
              $ E.filterWithKey (\(Name _ m _ _) _ -> m == main_mod) eenv
        ns_mm' = map (\(Name n m _ _) -> (n, m)) ns_mm
    in
    ns_mm'

isPrimRetTy :: S.KnownValues -> Expr -> Bool
isPrimRetTy kv e =
    let
        rel_t = extractValues . extractTypePolyBound $ returnType e
    in
    any (\t -> t == tyInt kv || t == tyBool kv) rel_t

tyVarNoMeas :: Measures -> TCValues -> [GhcInfo] -> Expr -> Bool
tyVarNoMeas meas tcv ghci e =
    let
        rel_t = extractValues . extractTypePolyBound $ returnType e
        rel_meas = E.filter (\e -> 
                            case filter notLH . argumentTypes . PresType . inTyForAlls $ typeOf e of
                                  [t] -> any (\t' -> isJust $ t' `specializes` t) rel_t
                                  _ -> False ) meas'
    in
    E.null rel_meas
    where
        meas_names = measureNames ghci
        meas_nameOcc = map (\(Name n md _ _) -> (n, md)) $ map symbolName meas_names
        meas' = E.filterWithKey (\(Name n m _ _) _ -> (n, m) `elem` meas_nameOcc) meas

        notLH t
            | TyCon n _ <- tyAppCenter t = n /= lhTC tcv
            | otherwise = False

tyVarRetTy :: Expr -> Bool
tyVarRetTy e =
    case returnType e of
        TyVar _ -> True
        _ -> False