blob: 37eadc510b0f706d6661097c67526472b6bce15b [file] [log] [blame] [edit]
-- |
-- Module : Minigent.Termination
-- Copyright : (c) Data61 2018-2019
-- Commonwealth Science and Research Organisation (CSIRO)
-- ABN 41 687 119 230
-- License : BSD3
--
-- The termination checking module
--
-- May be used qualified or unqualified.
module Minigent.Termination
( termCheck
, genGraphDotFile
, Assertion (..)
) where
import Minigent.Fresh
import Minigent.Syntax
import Minigent.Syntax.Utils
import Minigent.Environment
import Minigent.Syntax.PrettyPrint
import Control.Monad.State.Strict
import Data.Maybe (maybeToList, catMaybes)
import qualified Data.Map as M
import qualified Data.Set as S
import Data.List
-- A directed graph maps a node name to all reachable nodes from that node
type Node = String
type Graph = M.Map Node [Node]
-- Our environment, a mapping between program variables and fresh variables
type FreshVar = String
type Env = M.Map VarName FreshVar
type Error = String
type DotGraph = String
termCheck :: GlobalEnvironments -> ([Error], [(FunName, [Assertion], String)])
termCheck genvs = M.foldrWithKey go ([],[]) (defns genvs)
where
go :: FunName -> (VarName, Expr) -> ([Error], [(FunName, [Assertion], DotGraph)]) -> ([Error], [(FunName, [Assertion], DotGraph)])
go f (x,e) (errs, dumps) =
let (terminates, g, dotGraph) = fst $ runFresh unifVars (init f x e)
errs' = if terminates then
errs
else
("Error: Function " ++ f ++ " cannot be shown to terminate.") : errs
in
(errs', (f, g, dotGraph) : dumps)
-- Maps the function argument to a name, then runs the termination
-- assertion generator.
-- Returns:
-- ( true if the function terminates
-- , the list of assertions produced from the function
-- , the `dot' graph file for this particular termination graph
-- )
init :: FunName -> VarName -> Expr -> Fresh VarName (Bool, [Assertion], String)
init f x e = do
alpha <- fresh
let env = M.insert x alpha M.empty
(a,c) <- termAssertionGen env e
let graph = toGraph a
let goals = catMaybes c
-- If all goals are not nothing, and our path condition is met, then the function terminates
-- Otherwise, we cannot say anything about the function
let terminates = length goals == length c
&& all (\goal -> hasPathTo alpha goal graph
&& (not $ hasPathTo goal alpha graph)
) goals
return $
(
terminates,
a,
genGraphDotFile f graph [alpha] goals
)
termAssertionGen :: Env -> Expr -> Fresh VarName ([Assertion], [Maybe FreshVar])
termAssertionGen env expr
= case expr of
PrimOp _ es ->
join $ map (termAssertionGen env) es
Sig e _ ->
termAssertionGen env e
Apply f e -> do
a <- termAssertionGen env f
b <- termAssertionGen env e
return $ flatten [([], [getv env e]), a, b]
Struct fs ->
let es = map snd fs
in join $ map (termAssertionGen env) es
If b e1 e2 ->
join $ map (termAssertionGen env) [b, e1, e2]
Let x e1 e2 -> do
-- First evaluate the variable binding expression
a <- termAssertionGen env e1
-- Map our bound program variable to a new name and evaluate the rest
alpha <- fresh
let env' = M.insert x alpha env
res <- termAssertionGen env' e2
-- Generate assertion
let l = toAssertion env e1 (alpha :~:)
return $ flatten [(l,[]), res]
LetBang vs v e1 e2 ->
termAssertionGen env (Let v e1 e2)
Take r' f x e1 e2 -> do
alpha <- fresh
beta <- fresh
res <- termAssertionGen env e1
-- Update variable to fresh name bindings and generate assertions recursively
let env' = M.insert r' beta (M.insert x alpha env)
res' <- termAssertionGen env' e2
-- Generate assertions
let assertions = toAssertion env e1 (alpha :<:)
++ toAssertion env e1 (beta :~:)
return $ flatten [(assertions, []), res', res]
Put e1 f e2 -> do
alpha <- fresh
beta <- fresh
res <- termAssertionGen env e1
res' <- termAssertionGen env e2
let assertions = [alpha :<: beta]
++ toAssertion env e1 (beta :~:)
++ toAssertion env e2 (alpha :~:)
return $ flatten [(assertions, []), res', res]
Member e f ->
termAssertionGen env e
Case e1 _ x e2 y e3 -> do
alpha <- fresh
beta <- fresh
gamma <- fresh
res <- termAssertionGen env e1
let env' = M.insert x alpha env
res' <- termAssertionGen env' e2
let env'' = M.insert y gamma env
res'' <- termAssertionGen env'' e3
let assertions = toAssertion env e1 (beta :~:)
++ [alpha :<: beta, gamma :~: beta]
return $ flatten [(assertions, []), res, res', res'']
Esac e1 _ x e2 -> do
alpha <- fresh
beta <- fresh
res <- termAssertionGen env e1
let env' = M.insert x alpha env
res' <- termAssertionGen env' e2
let assertions = toAssertion env e1 (beta :~:)
++ [alpha :<: beta]
return $ flatten [(assertions, []), res, res']
-- All other cases, like literals and nonrecursive expressions
_ -> return ([],[])
where
toAssertion :: Env -> Expr -> (FreshVar -> Assertion) -> [Assertion]
toAssertion env e f =
case getv env e of
Just x -> [f x]
Nothing -> []
-- Returns the variable name from an environment if it exists, otherwise nothing
getv :: Env -> Expr -> Maybe FreshVar
getv env e =
case e of
Var v -> Just $ env M.! v
_ -> Nothing
join :: [Fresh VarName ([a], [b])] -> Fresh VarName ([a], [b])
join (e:es) = do
(a,b) <- e
(as,bs) <- join es
return (a ++ as, b ++ bs)
join [] = return ([],[])
flatten :: [([a], [b])] -> ([a], [b])
flatten (x:xs) =
let rest = flatten xs
in (fst x ++ fst rest, snd x ++ snd rest)
flatten [] = ([],[])
toGraph :: [Assertion] -> Graph
toGraph [] = mempty
toGraph (x:xs) =
case x of
(a :<: b) -> addEdge b a $ toGraph xs
(a :~: b) -> addEdge a b $ addEdge b a $ toGraph xs
where
addEdge a b =
M.insertWith (++) a [b]
hasPathTo :: Node -> Node -> Graph -> Bool
hasPathTo src dst g
= hasPathTo' src dst g S.empty
where
hasPathTo' :: Node -> Node -> Graph -> S.Set Node -> Bool
hasPathTo' s d g seen =
case M.lookup s g of
Nothing -> False
Just nbs ->
any (\n ->
n == dst ||
(notElem n seen &&
hasPathTo' n d g (S.insert n seen))
) nbs
-- To use:
-- run `dot -Tpdf graph.dot -o outfile.pdf`
-- where graph.dot is the output from this function.
genGraphDotFile :: String -> Graph -> [Node] -> [Node] -> String
genGraphDotFile name g args goals =
"digraph " ++ name ++
" {\n"
++ "\tforcelabels=true;\n"
++ highlight "blue" "argument" args
++ highlight "red" "goal" goals
++ intercalate "\n" (edges g)
++ "\n}"
where
pairs :: Graph -> [(Node,Node)]
pairs = concatMap (\(a, bs) -> map (\b -> (a,b)) bs) . M.assocs
edges :: Graph -> [String]
edges = map (\(a,b) -> "\t" ++ a ++ " -> " ++ b ++ ";") . pairs
highlight :: String -> String -> [Node] -> String
highlight color label nodes = "\t" ++ (concat . intersperse "\n" $
map (\n -> n ++ " [ color = " ++ color ++ ", xlabel = " ++ label ++ " ];\n") nodes)