packages feed

afv-0.0.0: src/Verify.hs

module Verify (verify) where

import Control.Monad
import Data.IORef
import Data.List
import Data.Maybe
import Math.SMT.Yices.Pipe
import Math.SMT.Yices.Syntax
import System.IO
import Text.Printf

import Model

-- | Verify a model with k-induction.
verify :: FilePath -> Int -> Model -> IO ()
verify _ maxK _ | maxK < 1 = error "max k can not be less than 1"
verify yices maxK m = do
  y <- createYicesPipe yices []
  mapM_ (verifyModel y maxK) models
  exitY y
  where
  assertions = [ a | a@(Assert _ _ _) <- actions m ]
  models = map (trimModel . trimAssertions m) assertions

-- | Remove all assertions except one.
trimAssertions :: Model -> Action -> Model
trimAssertions m a = m { actions = filter keep $ actions m }
  where
  keep b@(Assert _ _ _) = a == b
  keep _ = True

-- | Trim all unneeded stuff from a model.
trimModel :: Model -> Model
trimModel = id  --XXX

-- | Verify a trimmed model.
verifyModel :: YicesIPC -> Int -> Model -> IO ()
verifyModel y maxK m = do
  printf "verifying assertion %s ...\n" (intercalate "." path) >> hFlush stdout
  withLogic y $ do
    initEnv <- initEnv y m
    check initEnv initEnv [] 1
  where
  [path] = [ path | Assert _ path _ <- actions m ]
  check :: Env -> Env -> [ExpY] -> Int -> IO ()
  check _ _ _ k | k > maxK = printf "inconclusive: unable to proved step up to max k = %d\n" maxK
  check initEnv env asserts k = do
    (env, assert) <- transition y m env
    resultBasis <- checkBasis y m initEnv (assert : asserts)
    case resultBasis of
      Fail -> printf "FAILED: disproved basis in k = %d\n" k
      Pass -> do
        resultStep <- checkStep y assert
        case resultStep of
          Fail -> withLogic y $ check initEnv env (assert : asserts) (k + 1)
          Pass -> printf "passed: proved step in k = %d\n" k
        
data Result = Pass | Fail

-- | Check induction step.
checkStep :: YicesIPC -> ExpY -> IO Result
checkStep y assert = do
  r <- withLogic y $ do
    runCmds y [ASSERT $ NOT assert]
    checkLogic y
  return $ result r

-- | Check induction basis.
checkBasis :: YicesIPC -> Model -> Env -> [ExpY] -> IO Result
checkBasis y m env asserts = do
  r <- withLogic y $ do
    runCmds y [ ASSERT $ VarE (getVar env (State a)) := exprConst t c | a@(VS _ t c _) <- variables m ]
    runCmds y [ASSERT $ NOT $ AND asserts]
    checkLogic y
  return $ result r

result :: ResY -> Result
result a = case a of
  Sat _   -> Fail
  UnSat _ -> Pass
  InCon _ -> Pass

-- | Does operation in a new stack.
withLogic :: YicesIPC -> IO a -> IO a
withLogic y a = do
  runCmds y [PUSH]
  a <- a
  runCmds y [POP]
  return a

-- | Transition relation.  Returns assertion.
transition :: YicesIPC -> Model -> Env -> IO (Env, ExpY)  -- (new env, assertion)
transition y m env = do
  (env', assert) <- foldM (action y) (env, LitB True) (actions m)
  --runCmds y [ ASSERT (VarE (getVar env $ State a) := VarE (getVar env' $ State a)) | a <- variables m ]
  return (env', assert)

action :: YicesIPC -> (Env, ExpY) -> Action -> IO (Env, ExpY)
action y (env, assert) a = case a of
  Assign v e -> do
    e <- expr y env (typeOf v) e
    env <- addVar y env v
    runCmds y [ASSERT $ VarE (getVar env v) := e]
    return (env, assert)
  Assert a _ _ -> do
    a <- expr y env Bool a
    return (env, AND [assert, a])
  Assume a _ _ -> do
    a <- expr y env Bool a
    runCmds y [ASSERT a]
    return (env, assert)

expr :: YicesIPC -> Env -> Type -> E -> IO ExpY
expr y env t a = case a of
  Var a@(Volatile _ _ _) -> do
    env <- addVar y env a
    return $ VarE $ getVar env a
  Var a -> return $ VarE $ getVar env a

  Const a -> return $ exprConst t a

  Not a _ -> do
    a <- expr y env Bool a
    return $ NOT a

  And a b _ -> do
    a <- expr y env Bool a
    b <- expr y env Bool b
    return $ AND [a, b]

  Or  a b _ -> do
    a <- expr y env Bool a
    b <- expr y env Bool b
    return $ OR  [a, b]

  Mul a b _ -> do
    a <- expr y env t a
    b <- expr y env t b
    return $ a :*: b
  
  Div a b _  -> do
    a <- expr y env t a
    b <- expr y env t b
    return $ op a b
    where
    op = case t of
      Integer _ -> DIV
      _         -> (:/:)

  Mod a b _ -> do
    a <- expr y env t a
    b <- expr y env t b
    return $ MOD a b

  Add a b _ -> do
    a <- expr y env t a
    b <- expr y env t b
    return $ a :+: b

  Sub a b _ -> do
    a <- expr y env t a
    b <- expr y env t b
    return $ a :-: b

  Lt  a b n -> do
    let t' = unify n (typeOf a) (typeOf b)
    a <- expr y env t' a
    b <- expr y env t' b
    return $ a :< b

  Eq  a b n -> do
    let t' = unify n (typeOf a) (typeOf b)
    a <- expr y env t' a
    b <- expr y env t' b
    return $ a := b

  Mux a b c n -> do
    let t' = unify n (typeOf b) (typeOf c)
    a <- expr y env Bool a
    b <- expr y env t' b
    c <- expr y env t' c
    return $ IF a b c

exprConst :: Type -> Const -> ExpY
exprConst t a = case a of 
  CBool     a _ -> LitB a
  CInteger  a _ | t == Bool -> LitB $ a /= 0
                | otherwise -> LitI a
  CRational a _ -> LitR a




data Env = Env NextId [(V, String)]  -- Env nextId table

initEnv :: YicesIPC -> Model -> IO Env
initEnv y m = do
  nid <- newNextId
  foldM (addVar y) (Env nid []) $ map State $ variables m

addVar :: YicesIPC -> Env -> V -> IO Env
addVar y (Env nid table) v = do
  i <- nextId nid
  let name = printf "n%d" i
  runCmds y [DEFINE (name, VarT $ typeY v) Nothing]
  return $ Env nid $ replace (v, name) table

getVar :: Env -> V -> String
getVar (Env _ table) v = case lookup v table of
  Just a -> a
  Nothing -> error $ "Verify.getVar: variable not found: " ++ show v  ++ " in " ++ show (fst $ unzip table)

replace :: Eq a => (a, b) -> [(a, b)] -> [(a, b)]
replace a [] = [a]
replace (a, b) ((a', b') : c) | a == a'   = (a, b) : c
                              | otherwise = (a', b') : replace (a, b) c






typeY :: TypeOf a => a -> String
typeY a = case typeOf a of
  Void       -> error "Verify.typeY: void time"
  Bool       -> "bool"
  Integer  _ -> "int"
  Rational _ -> "real"



newtype NextId = NextId (IORef Int)

newNextId :: IO NextId
newNextId = newIORef 0 >>= (return . NextId)

nextId :: NextId -> IO Int
nextId (NextId a) = do
  i <- readIORef a
  writeIORef a $ i + 1
  return i


verbose :: Bool
verbose = False

runCmds :: YicesIPC -> [CmdY] -> IO ()
runCmds y cmds = do
  when verbose $ mapM_ (putStrLn . show) cmds
  runCmdsY' y cmds

checkLogic :: YicesIPC -> IO ResY
checkLogic y = do
  when verbose $ do
    putStrLn "(check)"
    hFlush stdout
  checkY y