g2-0.2.0.0: src/G2/Liquid/TyVarBags.hs
-- This module creates IR functions to extract an arbitrary (non-deterministic)
-- value of type a_i from a type of the form T a_1 ... a_n. If there are no
-- values with type a_i, the function calls `Assume False`.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
module G2.Liquid.TyVarBags ( TyVarBags
, InstFuncs
, existentialInstRed
, createBagAndInstFuncs
, extractTyVarCall
, wrapExtractCalls
, instTyVarCall
, existentialInstId
, postSeqExistentialInstId
, putExistentialInstInExprEnv
, putSymbolicExistentialInstInExprEnv
, addTicksToDeepSeqCases) where
import G2.Execution.Reducer
import G2.Language
import qualified G2.Language.ExprEnv as E
import G2.Language.Monad
import qualified G2.Language.Stack as Stck
import G2.Liquid.Types
import Control.Monad
import qualified Data.HashSet as S
import qualified Data.HashMap.Lazy as HM
import qualified Data.Map.Lazy as M
import qualified Data.Text as T
-- | The bag and instantiation functions rely on each other, so we have to make them together
createBagAndInstFuncs :: [Name] -- ^ Which types do we need bag functions for?
-> [Name] -- ^ Which types do we need instantiation functions for?
-> LHStateM ()
createBagAndInstFuncs bag_func_ns inst_func_ns = do
tenv <- typeEnv
let bag_func_ns' = relNames tenv S.empty bag_func_ns
bag_tenv = HM.filterWithKey (\n _ -> n `S.member` bag_func_ns') tenv
bag_names <- assignBagFuncNames bag_tenv
setTyVarBags bag_names
let inst_func_ns' = relNames tenv S.empty inst_func_ns
inst_tenv = HM.filterWithKey (\n _ -> n `S.member` inst_func_ns') tenv
inst_names <- assignInstFuncNames inst_tenv
setInstFuncs inst_names
createBagFuncs bag_names bag_tenv
createInstFuncs inst_names inst_tenv
relNames :: TypeEnv -> S.HashSet Name -> [Name] -> S.HashSet Name
relNames _ rel [] = rel
relNames tenv rel (n:ns) =
if S.member n rel
then relNames tenv rel ns
else relNames tenv (S.insert n rel) ns'
where
ns' = case HM.lookup n tenv of
Nothing -> ns
Just r -> namesList r ++ ns
createBagFuncs :: TyVarBags -- ^ Which types do we need bag functions for?
-> TypeEnv
-> LHStateM ()
createBagFuncs func_names tenv = do
mapM_ (uncurry (createBagFunc func_names)) (HM.toList tenv)
-- | Creates a mapping of type names to bag creation function names
assignBagFuncNames :: ExState s m => TypeEnv -> m TyVarBags
assignBagFuncNames tenv =
return . M.fromList
=<< mapM
(\(n@(Name n' m _ _), adt) -> do
let dc = head (dataCon adt)
bi = bound_ids adt
mkName i = Name (n' `T.append` "_create_bag_" `T.append` (T.pack . show $ i)) m 0 Nothing
fn <- mapM
(\(i, tbi) -> do
n_fn <- freshSeededNameN (mkName i)
let t = foldr (\ntb -> TyForAll ntb)
(TyFun (returnType dc) (TyVar tbi)) bi
return $ Id n_fn t)
$ zip [0 :: Int ..] bi
return (n, fn)
) (HM.toList tenv)
createBagFunc :: TyVarBags -> Name -> AlgDataTy -> LHStateM ()
createBagFunc func_names tn adt
| Just fs <- M.lookup tn func_names =
mapM_ (uncurry (createBagFunc' func_names tn adt)) $ zip fs (bound_ids adt)
| otherwise = error "createBagFunc: type not found"
createBagFunc' :: TyVarBags
-> Name
-> AlgDataTy
-> Id -- ^ The Id of the function to create
-> Id -- ^ The Id of the TyVar to extract
-> LHStateM ()
createBagFunc' func_names tn adt fn tyvar_id = do
bi <- freshIdsN $ map (const TYPE) (bound_ids adt)
adt_i <- freshIdN $ mkFullAppedTyCon tn (map TyVar bi) TYPE
cse <- createBagFuncCase func_names adt_i tyvar_id bi adt
let e = mkLams (map (TypeL,) bi) $ Lam TermL adt_i cse
insertE (idName fn) e
-- | Examines the passed `Id` adt_i, which is the ADT to extract the tyvar tyvar_id from,
-- and constructs an expression to actually nondeterministically extract a tyvar_id.
createBagFuncCase :: TyVarBags
-> Id
-> Id
-> [Id]
-> AlgDataTy
-> LHStateM Expr
createBagFuncCase func_names adt_i tyvar_id bi (DataTyCon { bound_ids = adt_bi
, data_cons = dc }) = do
bindee <- freshIdN (typeOf adt_i)
let ty_map = zip adt_bi (map TyVar bi)
alts <- mapM (createBagFuncCaseAlt func_names tyvar_id ty_map) dc
return $ Case (Var adt_i) bindee (typeOf $ head alts) alts
createBagFuncCase func_names adt_i tyvar_id bi (NewTyCon { bound_ids = adt_bi
, rep_type = rt }) = do
let rt' = foldr (uncurry retype) rt $ zip adt_bi (map TyVar bi)
cst = Cast (Var adt_i) (typeOf adt_i :~ rt')
clls <- extractTyVarCall func_names todo_emp tyvar_id cst
wrapExtractCalls clls
createBagFuncCase _ _ _ _ (TypeSynonym {}) =
error "creatBagFuncCase: TypeSynonyms unsupported"
createBagFuncCaseAlt :: TyVarBags -> Id -> [(Id, Type)] -> DataCon -> LHStateM Alt
createBagFuncCaseAlt func_names tyvar_id ty_map dc = do
let at = anonArgumentTypes dc
is <- freshIdsN at
let is' = foldr (uncurry retype) is ty_map
tyvar_id' = maybe tyvar_id id $ tyVarId =<< lookup tyvar_id ty_map
es <- return . concat =<< mapM (extractTyVarCall func_names todo_emp tyvar_id' . Var) is'
case null es of
True -> do
flse <- mkFalseE
return $ Alt (DataAlt dc is')
(Assume Nothing flse (Prim Undefined (TyVar tyvar_id)))
False -> return $ Alt (DataAlt dc is') (NonDet es)
where
tyVarId (TyVar i) = Just i
tyVarId _ = Nothing
todo_emp :: [a]
todo_emp = []
-- | Creates a set of expressions to get all TyVars i out of an
-- expression e.
extractTyVarCall :: TyVarBags
-> [(Id, Id)] -- ^ Mapping of TyVar Ids to Functions to create those TyVars
-> Id
-> Expr
-> LHStateM [Expr]
extractTyVarCall func_names is_fs i e
| TyVar i' <- t
, i == i' = return [e]
| TyCon n tc_t:ts <- unTyApp t
, Just fn <- M.lookup n func_names = do
let is = anonArgumentTypes (PresType tc_t)
ty_ars = map Type $ take (length is) ts
nds = map (\f -> App (mkApp (Var f:ty_ars)) e) fn
nds' <- mapM (extractTyVarCall func_names is_fs i) nds
return (concat nds')
| TyFun _ _ <- t = do
let ars_ty = anonArgumentTypes $ PresType t
tvs = tyVarIds . returnType $ PresType t
inst_fs <- getInstFuncs
inst_ars <- mapM (instTyVarCall' inst_fs is_fs) ars_ty
let call_f = mkApp $ e:inst_ars
cll <- if i `elem` tvs then extractTyVarCall func_names is_fs i call_f else return []
return cll
| otherwise = return []
where
t = typeOf e
wrapExtractCalls :: ExState s m => [Expr] -> m Expr
wrapExtractCalls clls = do
case null clls of
True -> do
-- flse <- mkFalseE
return (Var existentialInstId)
False -> return $ NonDet clls
-- | Creates functions to, for each type (T a_1 ... a_n), create a nondeterministic value.
-- Each a_1 ... a_n has an associated function, allowing the caller to decide how to instantiate
-- these values.
createInstFuncs :: InstFuncs -- ^ Which types do we need instantiation functions for?
-> TypeEnv
-> LHStateM ()
createInstFuncs func_names tenv = do
mapM_ (uncurry (createInstFunc func_names)) (HM.toList tenv)
-- | Creates a mapping of type names to instantatiation function names
assignInstFuncNames :: ExState s m => TypeEnv -> m InstFuncs
assignInstFuncNames tenv =
return . M.fromList
=<< mapM
(\(tn@(Name n m _ _), adt) -> do
let bi = bound_ids adt
fn <- freshSeededNameN (Name (n `T.append` "_inst_") m 0 Nothing)
let adt_i = mkFullAppedTyCon tn (map TyVar bi) TYPE
let t = foldr (\ntb -> TyForAll ntb)
(foldr (\i -> TyFun (TyVar i)) adt_i bi) bi
return (tn, Id fn t)
) (HM.toList tenv)
createInstFunc :: InstFuncs -> Name -> AlgDataTy -> LHStateM ()
createInstFunc func_names tn adt
| Just fn <- M.lookup tn func_names = do
bi <- freshIdsN $ map (const TYPE) (bound_ids adt)
inst_fs <- freshIdsN $ map TyVar bi
cse <- createInstFunc' func_names (zip bi inst_fs) adt
let e = mkLams (map (TypeL,) bi) $ mkLams (map (TermL,) inst_fs) cse
insertE (idName fn) e
| otherwise = error "createInstFunc: type not found"
createInstFunc' :: InstFuncs -> [(Id, Id)] -> AlgDataTy -> LHStateM Expr
createInstFunc' func_names is_fs (DataTyCon { data_cons = dcs }) = do
dc' <- mapM (\dc -> do
let apped_dc = mkApp (Data dc:map (Type . TyVar . fst) is_fs)
ars_ty = anonArgumentTypes dc
is_fs' = zipWith (\i (_, f) -> (i, f)) (leadingTyForAllBindings dc) is_fs
ars <- mapM (instTyVarCall' func_names is_fs') ars_ty
bnds <- mapM freshIdN ars_ty
let vrs = map Var bnds
let e = mkApp $ apped_dc:vrs
e' <- foldM wrapPrimsInCase e vrs
return $ Let (zip bnds ars) e') dcs
return (NonDet dc')
createInstFunc' _ _ (NewTyCon { rep_type = _ }) = do
-- rt_val <- instTyVarCall' func_names is_fs rt
return $ Cast undefined undefined
createInstFunc' _ _ _ = error "createInstFunc': unhandled datatype"
-- | Creates an instTyVarCall function call to create an expression of type t with appropriate TyVars
instTyVarCall :: ExState s m =>
InstFuncs
-> [(Id, Id)] -- ^ Mapping of TyVar Ids to Functions to create those TyVars
-> Type
-> m Expr
instTyVarCall func_names is_fs t = do
tUnit <- tyUnitT
ui <- freshIdN tUnit
cll <- instTyVarCall' func_names is_fs t
return $ Lam TermL ui cll
instTyVarCall' :: ExState s m =>
InstFuncs
-> [(Id, Id)] -- ^ Mapping of TyVar Ids to Functions to create those TyVars
-> Type
-> m Expr
instTyVarCall' func_names is_fs t
| TyVar i <- t
, Just f <- lookup i is_fs = do
return $ Var f
| TyVar i <- t = do
flse <- mkFalseE
return . Assume Nothing flse . Prim Undefined $ TyVar i
| TyCon n tc_t:ts <- unTyApp t
, Just fn <- M.lookup n func_names = do
let tyc_is = anonArgumentTypes (PresType tc_t)
ty_ts = take (length tyc_is) ts
ty_ars = map Type ty_ts
func_ars <- mapM (\t' -> case t' of
TyVar i
| Just i' <- lookup i is_fs -> return (Var i')
_ -> do
cll <- instTyVarCall' func_names is_fs t'
return cll) ty_ts
let_ids <- freshIdsN $ map typeOf func_ars
let bnds = zip let_ids func_ars
return . Let bnds . mkApp $ Var fn:ty_ars ++ map Var let_ids
| otherwise = do
let tfa = leadingTyForAllBindings $ PresType t
tfa_is = zipWith (\i1 (i2, _) -> (i1, TyVar i2)) tfa is_fs
rt = foldr (uncurry retype) (returnType $ PresType t) tfa_is
return $ SymGen SNoLog rt
-- | Primitive operation function calls do not force evaluation of the
-- underlying primitive value- the assumption is that this is already a literal
-- or a symbolic value. Thus, if we have a SymGen being passed to a primitive
-- operation, our rules will not know how to handle it.
-- Thus, we wrap SymGen's of primitive types in case statements.
wrapPrimsInCase :: ExState s m => Expr -> Expr -> m Expr
wrapPrimsInCase e e'
| isPrimType t = do
i <- freshIdN t
return $ Case e' i (typeOf e) [Alt Default e]
| otherwise = return e
where
t = typeOf e'
----------------------------------------
-- Existential Inst
-- Suppose a function returns a value with a polymorphic type, without taking
-- any of those types as arguments. This is common with functions that return
-- the "empty" case of data structures, such as Data.Map.empty.
-- In this case, we instantiate with an "existential" value,
-- that basically says some value may exist, but we do not know specifically what it is
existentialInstId :: Id
existentialInstId = Id (Name "EXISTENTIAL_INST_NAME" Nothing 0 Nothing) TyUnknown
postSeqExistentialInstId :: Id
postSeqExistentialInstId = Id (Name "POST_SEQ_EXISTENTIAL_INST_NAME" Nothing 0 Nothing) TyUnknown
-- | Place this in a Tick in the first Alt of a Case, to treat the case normally,
-- even if the existential Id is in the bindee
existentialCaseName :: Name
existentialCaseName = Name "EXISTENTIAL_CASE_NAME" Nothing 0 Nothing
putExistentialInstInExprEnv :: State t -> State t
putExistentialInstInExprEnv s@(State { expr_env = eenv }) =
s { expr_env = E.insert
(idName existentialInstId)
(Var existentialInstId)
eenv }
putSymbolicExistentialInstInExprEnv :: State t -> State t
putSymbolicExistentialInstInExprEnv s@(State { expr_env = eenv }) =
s { expr_env = E.insertSymbolic
existentialInstId
eenv
}
{-# INLINE existentialInstRed #-}
existentialInstRed :: Monad m => Reducer m () t
existentialInstRed = mkSimpleReducer (const ()) existInstRedRules
existInstRedRules :: Monad m => RedRules m () t
existInstRedRules rv s@(State { expr_env = eenv
, curr_expr = CurrExpr Evaluate e })
b@(Bindings { name_gen = ng })
| Var i <- e
, i == existentialInstId =
let
s' = s { expr_env = E.insertSymbolic i eenv
, curr_expr = CurrExpr Return e }
in
return (InProgress, [(s', rv)], b)
| Case (Var i) bnd _ ([Alt (DataAlt _ params) (Tick (NamedLoc n) ae)]) <- e
, i == existentialInstId
, n == existentialCaseName =
let
(n_bnd, ng') = freshSeededName (idName bnd) ng
(n_params, ng'') = freshSeededNames (map idName params) ng'
eenv' = E.insertSymbolic postSeqExistentialInstId eenv
eenv'' = foldr (\en -> E.insert en (Var existentialInstId)) eenv' n_params
n_e = rename (idName bnd) n_bnd $ foldr (uncurry rename) ae (zip (map idName params) n_params)
in
return ( InProgress
, [(s { expr_env = eenv''
, curr_expr = CurrExpr Evaluate n_e }, rv)]
, b { name_gen = ng'' })
| Case (Var i) _ _ ([Alt _ (Tick (NamedLoc n) ae)]) <- e
, i == existentialInstId
, n == existentialCaseName =
let
eenv' = E.insertSymbolic postSeqExistentialInstId eenv
in
return ( InProgress
, [(s { expr_env = eenv'
, curr_expr = CurrExpr Evaluate ae }, rv)]
, b)
| Case (Var i) _ _ _ <- e
, i == existentialInstId =
let
s' = s { curr_expr = CurrExpr Return (Var i) }
in
return (InProgress, [(s', rv)], b)
existInstRedRules rv s@(State { curr_expr = CurrExpr Return e
, exec_stack = stck }) b
| Just (AssumeFrame _, stck') <- Stck.pop stck
, Var i <- e
, i == existentialInstId =
return (InProgress, [(s { exec_stack = stck' }, rv)], b)
| Just (AssertFrame _ _, stck') <- Stck.pop stck
, Var i <- e
, i == existentialInstId =
return (InProgress, [(s { exec_stack = stck' }, rv)], b)
existInstRedRules rv s b = return (NoProgress, [(s, rv)], b)
addTicksToDeepSeqCases :: Walkers -> State t -> State t
addTicksToDeepSeqCases w s@(State { expr_env = eenv }) =
s { expr_env = foldr addTicksToDeepSeqCases' eenv (map idName $ M.elems w)}
addTicksToDeepSeqCases' :: Name -> ExprEnv -> ExprEnv
addTicksToDeepSeqCases' n eenv =
case E.lookup n eenv of
Just e -> E.insert n (modify addTicksToDeepSeqCases'' e) eenv
Nothing -> eenv
addTicksToDeepSeqCases'' :: Expr -> Expr
addTicksToDeepSeqCases'' (Case e i t (Alt am ae:as)) =
Case e i t $ Alt am (Tick (NamedLoc existentialCaseName) ae):as
addTicksToDeepSeqCases'' e = e