module Data.SBV.Plugin.Analyze (analyzeBind) where
import GhcPlugins
import Control.Monad.Reader
import System.Exit hiding (die)
import Data.IORef
import Data.Char (isAlpha, isAlphaNum, toLower, isUpper)
import Data.List (intercalate, partition, nub, sort, sortBy, isPrefixOf)
import Data.Maybe (listToMaybe, fromMaybe)
import Data.Ord (comparing)
import qualified Data.Map as M
import qualified Data.SBV as S hiding (proveWith, proveWithAny)
import qualified Data.SBV.Dynamic as S
import qualified Data.SBV.Internals as S
import qualified Control.Exception as C
import Data.SBV.Plugin.Common
import Data.SBV.Plugin.Data
analyzeBind :: Config -> CoreBind -> CoreM ()
analyzeBind cfg@Config{sbvAnnotation, cfgEnv} = go
where go (NonRec b e) = bind (b, e)
go (Rec binds) = mapM_ bind binds
bind (b, e) = mapM_ work (sbvAnnotation b)
where work (SBV opts)
| Just s <- hasSkip opts = liftIO $ putStrLn $ "[SBV] " ++ showSpan cfg b topLoc ++ " Skipping " ++ show (showSDoc (flags cfgEnv) (ppr b)) ++ ": " ++ s
| Uninterpret `elem` opts = return ()
| True = liftIO $ prove cfg opts b topLoc e
hasSkip opts = listToMaybe [s | Skip s <- opts]
topLoc = bindSpan b
prove :: Config -> [SBVOption] -> Var -> SrcSpan -> CoreExpr -> IO ()
prove cfg@Config{isGHCi} opts b topLoc e = do
success <- safely $ proveIt cfg opts (topLoc, b) e
unless (success || isGHCi || IgnoreFailure `elem` opts) $ do
putStrLn $ "[SBV] Failed. (Use option '" ++ show IgnoreFailure ++ "' to continue.)"
exitFailure
safely :: IO Bool -> IO Bool
safely a = a `C.catch` bad
where bad :: C.SomeException -> IO Bool
bad e = do print e
return False
proveIt :: Config -> [SBVOption] -> (SrcSpan, Var) -> CoreExpr -> IO Bool
proveIt cfg@Config{cfgEnv, sbvAnnotation} opts (topLoc, topBind) topExpr = do
solverConfigs <- pickSolvers opts
let verbose = Verbose `elem` opts
qCheck = QuickCheck `elem` opts
runProver prop
| qCheck = Left `fmap` S.svQuickCheck prop
| True = Right `fmap` S.proveWithAny [s{S.verbose = verbose} | s <- solverConfigs] prop
loc = "[SBV] " ++ showSpan cfg topBind topLoc
slvrTag = ", using " ++ tag ++ "."
where tag = case solverConfigs of
[] -> "no solvers"
[x] -> show x
[x, y] -> show x ++ " and " ++ show y
xs -> intercalate ", " (map show (init xs)) ++ ", and " ++ show (last xs)
putStrLn $ "\n" ++ loc ++ (if qCheck then " QuickChecking " else " Proving ") ++ show (sh topBind) ++ slvrTag
(finalResult, finalUninterps) <- do
finalResult <- runProver (res cfgEnv)
finalUninterps <- readIORef (rUninterpreted cfgEnv)
return (finalResult, finalUninterps)
case finalResult of
Right (solver, sres@(S.ThmResult smtRes)) -> do
let success = case smtRes of
S.Unsatisfiable{} -> True
S.Satisfiable{} -> False
S.Unknown{} -> False
S.ProofError{} -> False
S.TimeOut{} -> False
putStr $ "[" ++ show solver ++ "] "
print sres
let unintVals = filter ((`notElem` uninteresting cfgEnv) . snd) $ nub $ sortBy (comparing fst) [vt | (vt, _) <- finalUninterps]
unless (success || null unintVals) $ do
let plu | length finalUninterps > 1 = "s:"
| True = ":"
shUI (e, t) = (showSDoc (flags cfgEnv) (ppr (getSrcSpan e)), sh e, sh t)
ls = map shUI unintVals
len1 = maximum (0 : [length s | (s, _, _) <- ls])
len2 = maximum (0 : [length s | (_, s, _) <- ls])
pad n s = take n (s ++ repeat ' ')
put (a, b, c) = putStrLn $ " [" ++ pad len1 a ++ "] " ++ pad len2 b ++ " :: " ++ c
putStrLn $ "[SBV] Counter-example might be bogus due to uninterpreted constant" ++ plu
mapM_ put ls
return success
Left success -> return success
where debug = Debug `elem` opts
res initEnv = do
v <- runReaderT (symEval topExpr) initEnv{curLoc = topLoc}
case v of
Base r -> return r
r -> error $ "Impossible happened. Final result reduced to a non-base value: " ++ showSDocUnsafe (ppr r)
die :: SrcSpan -> String -> [String] -> a
die loc w es = error $ concatMap ("\n" ++) $ tag ("Skipping proof. " ++ w ++ ":") : map tab es
where marker = "[SBV] " ++ showSpan cfg topBind loc
tag s = marker ++ " " ++ s
tab s = replicate (length marker) ' ' ++ " " ++ s
tbd :: String -> [String] -> Eval Val
tbd w ws = do Env{curLoc} <- ask
die curLoc w ws
sh o = showSDoc (flags cfgEnv) (ppr o)
symEval :: CoreExpr -> Eval Val
symEval e = do let (bs, body) = collectBinders (pushLetLambda e)
Env{curLoc} <- ask
let mbListSize = listToMaybe [n | ListSize n <- opts]
bodyType <- getType curLoc (exprType body)
let (extraArgs, finalType) = walk bodyType []
where walk (KFun d c) sofar = walk c (d:sofar)
walk k sofar = (reverse sofar, k)
case finalType of
KBase S.KBool -> do
argKs <- mapM (\b -> getType (getSrcSpan b) (varType b) >>= \bt -> return (b, bt)) bs
let mkVar ((b, k), mbN) = do sv <- mkSym mbListSize curLoc (Just (idType b)) k (mbN `mplus` Just (sh b))
return ((b, k), sv)
bArgs <- mapM (lift . mkVar) (zip argKs (concat [map Just ns | Names ns <- opts] ++ repeat Nothing))
bRes <- local (\env -> env{envMap = foldr (uncurry M.insert) (envMap env) bArgs}) (go body)
let feed [] sres = return sres
feed (k:ks) (Func _ f) = do sv <- lift $ mkSym mbListSize curLoc Nothing k Nothing
f sv >>= feed ks
feed ks v = error $ "Impossible! Left with extra args to apply on a non-function: " ++ sh (ks, v)
feed extraArgs bRes
_ -> die curLoc "Non-boolean property declaration" (concat [ ["Found : " ++ sh (exprType e)]
, ["Returning: " ++ sh (exprType body) | not (null bs)]
, ["Expected : Bool" ++ if null bs then "" else " result"]
])
where
pushLetLambda (Let b (Lam x bd)) = Lam x (pushLetLambda (Let b bd))
pushLetLambda o = o
mkSym :: Maybe Int -> SrcSpan -> Maybe Type -> SKind -> Maybe String -> S.Symbolic Val
mkSym mbLs curLoc mbBType = sym
where tinfo k = case mbBType of
Nothing -> "Kind: " ++ sh k
Just t -> "Type: " ++ sh t
sym (KBase k) nm = do v <- S.svMkSymVar Nothing k nm
return (Base v)
sym (KTup ks) nm = do let ns = map (\i -> (++ ("_" ++ show i)) `fmap` nm) [1 .. length ks]
vs <- zipWithM sym ks ns
return $ Tup vs
sym (KLst ks) nm = do let ls = fromMaybe bad mbLs
bad = die curLoc "List-argument found, with no size info"
[ "Name: " ++ fromMaybe "anonymous" nm
, tinfo (KLst ks)
, "Hint: Use the \"ListSize\" annotation"
]
ns = map (\i -> (++ ("_" ++ show i)) `fmap` nm) [1 .. ls]
vs <- zipWithM sym (replicate ls ks) ns
return (Lst vs)
sym k nm = die curLoc "Unsupported symbolic input" [ "Name: " ++ show nm
, tinfo k
]
isUninterpretedBinding :: Var -> Bool
isUninterpretedBinding v = any (Uninterpret `elem`) [opt | SBV opt <- sbvAnnotation v]
go :: CoreExpr -> Eval Val
go (Tick t e) = local (\envMap -> envMap{curLoc = tickSpan t (curLoc envMap)}) $ go e
go e = tgo (exprType e) e
debugTrace s w
| debug = trace ("--> " ++ s) w
| True = w
tgo :: Type -> CoreExpr -> Eval Val
tgo t e | debugTrace (sh (e, t)) False = undefined
tgo t (Var v) = do Env{envMap, coreMap} <- ask
k <- getType (getSrcSpan v) t
case (v, k) `M.lookup` envMap of
Just b -> return b
Nothing -> case v `M.lookup` coreMap of
Just b -> if isUninterpretedBinding v
then uninterpret t v
else go b
Nothing -> debugTrace ("Uninterpreting: " ++ sh (v, k, nub $ sort $ map (fst . fst) (M.toList envMap)))
$ uninterpret t v
tgo t e@(Lit l) = do Env{machWordSize} <- ask
case l of
MachChar{} -> unint
MachStr{} -> unint
MachNullAddr -> unint
MachLabel{} -> unint
MachInt i -> return $ Base $ S.svInteger (S.KBounded True machWordSize) i
MachInt64 i -> return $ Base $ S.svInteger (S.KBounded True 64 ) i
MachWord i -> return $ Base $ S.svInteger (S.KBounded False machWordSize) i
MachWord64 i -> return $ Base $ S.svInteger (S.KBounded False 64 ) i
MachFloat f -> return $ Base $ S.svFloat (fromRational f)
MachDouble d -> return $ Base $ S.svDouble (fromRational d)
LitInteger i it -> do k <- getType noSrcSpan it
case k of
KBase b -> return $ Base $ S.svInteger b i
_ -> error $ "Impossible: The type for literal resulted in non base kind: " ++ sh (e, k)
where unint = do Env{flags} <- ask
k <- getType noSrcSpan t
nm <- mkValidName (showSDoc flags (ppr e))
case k of
KBase b -> return $ Base $ S.svUninterpreted b nm Nothing []
_ -> error $ "Impossible: The type for literal resulted in non base kind: " ++ sh (e, k)
tgo tFun orig@App{} = do
reduced <- betaReduce orig
Env{specials} <- ask
let isEq (App (App (Var v) (Type _)) dict) | isReallyADictionary dict, Just f <- isEquality specials v = Just f
isEq _ = Nothing
isTup (Var v) = isTuple specials v
isTup (App f (Type _)) = isTup f
isTup _ = Nothing
isLst (Var v) = isList specials v
isLst (App f (Type _)) = isLst f
isLst _ = Nothing
isSpecial e = isEq e `mplus` isTup e `mplus` isLst e
case isSpecial reduced of
Just f -> debugTrace ("Special located: " ++ sh (orig, f)) $ return f
Nothing -> case reduced of
App (App (Var v) (Type t)) dict | isReallyADictionary dict -> do
Env{envMap} <- ask
k <- getType (getSrcSpan v) t
case (v, k) `M.lookup` envMap of
Just b -> return b
Nothing -> do Env{coreMap} <- ask
case v `M.lookup` coreMap of
Just e -> tgo tFun (App (App e (Type t)) dict)
Nothing -> tgo tFun (Var v)
App (Var v) (Type t) -> do
Env{coreMap} <- ask
case v `M.lookup` coreMap of
Just e -> tgo tFun (App e (Type t))
Nothing -> tgo tFun (Var v)
App (Let (Rec bs) f) a -> go (Let (Rec bs) (App f a))
App f e -> do
func <- go f
arg <- go e
case (func, arg) of
(Func _ sf, sv) -> sf sv
_ -> error $ "[SBV] Impossible happened. Got an application with mismatched types: " ++ sh [(f, func), (e, arg)]
e -> go e
tgo _ (Lam b body) = do
k <- getType (getSrcSpan b) (varType b)
Env{envMap} <- ask
return $ Func (Just (sh b)) $ \s -> local (\env -> env{envMap = M.insert (b, k) s envMap}) (go body)
tgo _ (Let (NonRec b e) body) = local (\env -> env{coreMap = M.insert b e (coreMap env)}) (go body)
tgo _ (Let (Rec bs) body) = local (\env -> env{coreMap = foldr (uncurry M.insert) (coreMap env) bs}) (go body)
tgo _ e@(Case ce caseBinder caseType alts)
= do sce <- go ce
let isDefault (DEFAULT, _, _) = True
isDefault _ = False
(defs, nonDefs) = partition isDefault alts
walk ((p, bs, rhs) : rest) =
do mr <- match (bindSpan caseBinder) sce p bs
case mr of
Just (m, bs') -> do let result = local (\env -> env{envMap = foldr (uncurry M.insert) (envMap env) bs'}) $ go rhs
if null rest
then result
else choose m result (walk rest)
Nothing -> caseTooComplicated "with-complicated-match" ["MATCH " ++ sh (ce, p), " --> " ++ sh rhs]
walk [] = caseTooComplicated "with-non-exhaustive-match" []
k <- getType (getSrcSpan caseBinder) caseType
local (\env -> env{envMap = M.insert (caseBinder, k) sce (envMap env)}) $ walk (nonDefs ++ defs)
where caseTooComplicated w [] = tbd ("Unsupported case-expression (" ++ w ++ ")") [sh e]
caseTooComplicated w xs = tbd ("Unsupported case-expression (" ++ w ++ ")") $ [sh e, "While Analyzing:"] ++ xs
choose t tb fb = case S.svAsBool t of
Nothing -> do stb <- tb
sfb <- fb
case (stb, sfb) of
(Base a, Base b) -> return $ Base $ S.svIte t a b
_ -> caseTooComplicated "with-non-base-alternatives" []
Just True -> tb
Just False -> fb
match :: SrcSpan -> Val -> AltCon -> [Var] -> Eval (Maybe (S.SVal, [((Var, SKind), Val)]))
match sp a c bs = case c of
DEFAULT -> return $ Just (S.svTrue, [])
LitAlt l -> do b <- go (Lit l)
return $ Just (a `eqVal` b, [])
DataAlt dc -> do Env{envMap, destMap} <- ask
k <- getType sp (dataConRepType dc)
let wid = dataConWorkId dc
case (wid, k) `M.lookup` envMap of
Just (Base b) -> return $ Just (a `eqVal` Base b, [])
_ -> case wid `M.lookup` destMap of
Nothing -> return Nothing
Just f -> do bts <- mapM (\b -> getType (getSrcSpan b) (varType b) >>= \bt -> return (b, bt)) bs
return $ Just (f a bts)
tgo t (Cast e c)
= debugTrace ("Going thru a Cast: " ++ sh c) $ tgo t e
tgo _ (Tick t e) = local (\envMap -> envMap{curLoc = tickSpan t (curLoc envMap)}) $ go e
tgo _ (Type t)
= do Env{curLoc} <- ask
k <- getType curLoc t
return (Typ k)
tgo _ e@Coercion{}
= tbd "Unsupported coercion-expression" [sh e]
isBetaReducable (Type _) = True
isBetaReducable e = isReallyADictionary e
betaReduce :: CoreExpr -> Eval CoreExpr
betaReduce orig@(App f a) = do
rf <- betaReduce f
if not (isBetaReducable a)
then return (App rf a)
else do let chaseVars :: CoreExpr -> Eval CoreExpr
chaseVars (Var x) = do Env{coreMap} <- ask
case x `M.lookup` coreMap of
Nothing -> return (Var x)
Just b -> chaseVars b
chaseVars (Tick _ x) = chaseVars x
chaseVars x = return x
func <- chaseVars rf
case func of
Lam x b -> do reduced <- betaReduce $ substExpr (ppr "SBV.betaReduce") (extendSubstList emptySubst [(x, a)]) b
() <- debugTrace ("Beta reduce:\n" ++ sh (orig, reduced)) $ return ()
return reduced
_ -> return (App rf a)
betaReduce e = return e
isReallyADictionary :: CoreExpr -> Bool
isReallyADictionary (App f _) = isReallyADictionary f
isReallyADictionary (Var v) = "$" `isPrefixOf` unpackFS (occNameFS (occName (varName v)))
isReallyADictionary _ = False
uninterpret :: Type -> Var -> Eval Val
uninterpret t v = do
Env{rUninterpreted, flags} <- ask
prevUninterpreted <- liftIO $ readIORef rUninterpreted
case (v, t) `lookup` prevUninterpreted of
Just (_, val) -> return val
Nothing -> do let (tvs, t') = splitForAllTys t
(args, res) = splitFunTys t'
sp = getSrcSpan v
argKs <- mapM (getType sp) args
resK <- getType sp res
nm <- mkValidName $ showSDoc flags (ppr v)
let fVal = wrap tvs $ walk argKs (nm, resK) []
liftIO $ modifyIORef rUninterpreted (((v, t), (nm, fVal)) :)
return fVal
where walk :: [SKind] -> (String, SKind) -> [S.SVal] -> Val
walk [] (nm, k) args = case k of
KBase b -> Base $ S.svUninterpreted b nm Nothing (reverse args)
_ -> error $ "Not yet supported uninterpreted type with non-base type: " ++ showSDocUnsafe (ppr k)
walk (_:as) nmk args = Func Nothing $ \a -> case a of
Base p -> return (walk as nmk (p:args))
_ -> return (walk as nmk args)
wrap [] f = f
wrap (_:ts) f = Func Nothing $ \(Typ _) -> return (wrap ts f)
mkValidName :: String -> Eval String
mkValidName name =
do Env{rUsedNames} <- ask
usedNames <- liftIO $ readIORef rUsedNames
let unm = unSMT $ genSym usedNames name
liftIO $ modifyIORef rUsedNames (unm :)
return $ escape unm
where genSym bad nm
| nm `elem` bad = head [nm' | i <- [(0::Int) ..], let nm' = nm ++ "_" ++ show i, nm' `notElem` bad]
| True = nm
unSMT nm
| map toLower nm `elem` S.smtLibReservedNames
= if not (null nm) && isUpper (head nm)
then "sbv" ++ nm
else "sbv_" ++ nm
| True
= nm
escape nm | isAlpha (head nm) && all isGood (tail nm) = nm
| True = "|" ++ map tr nm ++ "|"
isGood c = isAlphaNum c || c == '_'
tr '|' = '_'
tr '\\' = '_'
tr c = c
getType :: SrcSpan -> Type -> Eval SKind
getType sp typ = do let (tvs, typ') = splitForAllTys typ
(args, res) = splitFunTys typ'
argKs <- mapM (getType sp) args
resK <- getComposite res
return $ wrap tvs $ foldr KFun resK argKs
where wrap ts f = foldr (KFun . mkUserSort) f ts
mkUserSort v = KBase (S.KUserSort (show (occNameFS (occName (varName v)))) (Left "sbvPlugin"))
getComposite :: Type -> Eval SKind
getComposite t = case splitTyConApp_maybe t of
Just (k, ts) | isTupleTyCon k -> KTup `fmap` mapM (getType sp) ts
Just (k, [a]) | listTyCon == k -> KLst `fmap` getType sp a
_ -> KBase `fmap` getBaseType t
getBaseType :: Type -> Eval S.Kind
getBaseType bt = do
Env{tcMap} <- ask
case grabTCs (splitTyConApp_maybe bt) of
Just k -> case k `M.lookup` tcMap of
Just knd -> return knd
Nothing -> unknown
_ -> unknown
where
grabTCs Nothing = Nothing
grabTCs (Just (top, ts)) = do as <- walk ts []
return (top, as)
walk [] sofar = Just $ reverse sofar
walk (a:as) sofar = case splitTyConApp_maybe a of
Just (ac, []) -> walk as (ac:sofar)
_ -> Nothing
unknown = do Env{flags, rUITypes} <- ask
uiTypes <- liftIO $ readIORef rUITypes
case bt `lookup` uiTypes of
Just k -> return k
Nothing -> do nm <- mkValidName $ showSDoc flags (ppr bt)
let k = S.KUserSort nm $ Left $ "originating from sbvPlugin: " ++ showSDoc flags (ppr sp)
liftIO $ modifyIORef rUITypes ((bt, k) :)
return k