cryptol-2.2.0: src/Cryptol/TypeCheck/Solver/FinOrd.hs
-- |
-- Module : $Header$
-- Copyright : (c) 2013-2015 Galois, Inc.
-- License : BSD3
-- Maintainer : cryptol@galois.com
-- Stability : provisional
-- Portability : portable
--
-- This module contains machinery to reason about ordering of
-- variables, their finiteness, and their possible intervals.
{-# LANGUAGE Safe #-}
{-# LANGUAGE PatternGuards, TypeSynonymInstances #-}
module Cryptol.TypeCheck.Solver.FinOrd
( OrdFacts, AssertResult(..)
, noFacts, addFact
, isKnownLeq
, knownInterval
, ordFactsToGoals
, ordFactsToProps
, dumpDot
, dumpDoc
, IsFact(..)
) where
import Cryptol.TypeCheck.Solver.InfNat
import Cryptol.TypeCheck.Solver.Interval
import Cryptol.TypeCheck.AST
import Cryptol.TypeCheck.InferTypes
import Cryptol.TypeCheck.TypeMap
import Cryptol.Parser.Position
import qualified Cryptol.Utils.Panic as P
import Cryptol.Utils.PP(Doc,pp,vcat,text,(<+>))
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Map (Map)
import qualified Data.Map as Map
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Data.Maybe(fromMaybe,maybeToList)
import Control.Monad(guard)
-- Please change this, if renaming the module.
panic :: String -> [String] -> a
panic x y = P.panic ("Cryptol.TypeCheck.Solver.FinOrd." ++ x) y
-- | Add a `(>=)` or `fin` goal into the model.
-- Assumes that the types are normalized (i.e., no type functions).
class IsFact t where
factProp :: t -> Prop
factChangeProp :: t -> Prop -> t
factSource :: t -> EdgeSrc
instance IsFact Goal where
factProp = goal
factChangeProp g p = g { goal = p }
factSource g = FromGoal (goalSource g) (goalRange g)
instance IsFact Prop where
factProp = id
factChangeProp _ x = x
factSource _ = NoGoal
addFact :: IsFact t => t -> OrdFacts -> AssertResult
addFact g m =
case tNoUser (factProp g) of
TCon (PC PFin) [t] ->
let (x,m1) = nameTerm m t
in insertFin src x m1
TCon (PC PGeq) [t1,t2] ->
let (x,m1) = nameTerm m t1
(y,m2) = nameTerm m1 t2
in insertLeq src y x m2
_ -> OrdCannot
where
src = factSource g
-- | Possible outcomes, when asserting a fact.
data AssertResult
= OrdAdded OrdFacts -- ^ We added a new fact
| OrdAlreadyKnown -- ^ We already knew this
| OrdImprove Type Type -- ^ Only if the two types are equal
| OrdImpossible -- ^ The fact is known to be false
| OrdCannot -- ^ We could not perform opertaion.
deriving Show
-- | Query if the one type is known to be smaller than, or equal to, the other.
-- Assumes that the type is simple (i.e., no type functions).
isKnownLeq :: OrdFacts -> Type -> Type -> Bool
isKnownLeq m t1 t2 =
let (x,m1) = nameTerm m t1
(y,m2) = nameTerm m1 t2
in isLeq m2 x y
-- | Compute an interval, that we know definately contains the given type.
-- Assumes that the type is normalized (i.e., no type functions).
knownInterval :: OrdFacts -> Type -> Interval
knownInterval m t =
fromMaybe anything $
do a <- numType t
return $
case (cvtLower (getLowerBound m a), cvtUpper (getUpperBound m a)) of
(x,(y,z)) -> Interval { lowerBound = x
, upperBound = y
, isFinite = z
}
where
cvtLower (Nat'' x) = Nat x
cvtLower FinNat'' = panic "knownInterval"
[ "`FinNat` used as a lower bound for:"
, show t
]
cvtLower Inf'' = Inf
cvtUpper (Nat'' x) = (Nat x, True)
cvtUpper FinNat'' = (Inf, True)
cvtUpper Inf'' = (Inf, False)
ordFactsToGoals :: OrdFacts -> [Goal]
ordFactsToGoals = ordFactsToList onlyGoals
where
onlyGoals (FromGoal c r) = Just $ \p -> Goal { goalSource = c
, goalRange = r
, goal = p }
onlyGoals NoGoal = Nothing
ordFactsToProps :: OrdFacts -> [Prop]
ordFactsToProps = ordFactsToList (\_ -> Just id)
ordFactsToList :: (EdgeSrc -> Maybe (Prop -> a)) -> OrdFacts -> [a]
ordFactsToList consider (OrdFacts m ts) = concatMap getGoals (Map.toList m)
where
getGoals (ty, es) =
do (lower,notNum) <-
case ty of
NTNat FinNat'' -> []
NTNat Inf'' -> []
NTNat (Nat'' n) -> guard (n > 0) >> [ (tNum n, False) ]
NTVar v -> [ (TVar v, True) ]
NTNamed x -> [ (getNamed x, True) ]
Edge { target = t, eSource = src } <- Set.toList (above es)
f <- maybeToList (consider src)
g <- case t of
NTNat FinNat'' -> guard notNum >> [ pFin lower ]
NTNat Inf'' -> []
NTNat (Nat'' n) -> guard notNum >> [ tNum n >== lower ]
NTVar x -> [ TVar x >== lower ]
NTNamed x -> [ getNamed x >== lower ]
return (f g)
getNamed x = case IntMap.lookup x (nameToType ts) of
Just t -> t
Nothing -> panic "ordFactsToList" [ "Missing name" ]
--------------------------------------------------------------------------------
data OrdFacts = OrdFacts (Map NumType Edges) OrdTerms
deriving Show
-- | Names for non-primitive terms
data OrdTerms = OrdTerms
{ typeToName :: TypeMap Int
-- ^ Maps terms to their name
, nameToType :: IntMap Type
-- ^ Maps names to terms
, nextId :: Int -- ^ For naming new terms.
} deriving Show
data Edges = Edges { above :: Set Edge, below :: Set Edge }
deriving Show
data Edge = Edge { target :: NumType, eSource :: EdgeSrc }
deriving Show
-- | Where did this edge come from?
-- This is used so that we can turn edges back into goals.
data EdgeSrc = FromGoal ConstraintSource Range
| NoGoal
deriving Show
instance Eq Edge where
x == y = target x == target y
instance Ord Edge where
compare x y = compare (target x) (target y)
{- | A varaation on `Nat'`, which allows us to support `fin` constraints:
we add an extra element `FinNat''`, which is larger than all natural numbers,
but smaller than infinity. Then, we can express `fin t` as `t <= fin`.
This is only internal to the implementation and is not visible outside
this module. -}
data Nat'' = Nat'' Integer
| FinNat'' -- Upper bound for known finite
| Inf''
deriving (Eq,Ord,Show)
-- NOTE: It is important that constants come before variables in the
-- ordering (used in `insNode`)
data NumType = NTNat Nat'' | NTVar TVar | NTNamed Int
deriving (Eq,Ord,Show)
nameTerm :: OrdFacts -> Type -> (NumType, OrdFacts)
nameTerm fs t | Just n <- numType t = (n, fs)
nameTerm fs@(OrdFacts xs ts) t =
case lookupTM t (typeToName ts) of
Just n -> (NTNamed n, fs)
Nothing ->
let name = nextId ts
ts1 = OrdTerms { nameToType = IntMap.insert name t (nameToType ts)
, typeToName = insertTM t name (typeToName ts)
, nextId = name + 1
}
in (NTNamed name, OrdFacts xs ts1)
zero :: NumType
zero = NTNat (Nat'' 0)
-- | A finite number larger than all ordinary numbers.
-- Used to represent `fin` predicates.
fin :: NumType
fin = NTNat FinNat''
inf :: NumType
inf = NTNat Inf''
numType :: Type -> Maybe NumType
numType ty =
case tNoUser ty of
TCon (TC (TCNum n)) _ -> Just $ NTNat $ Nat'' n
TCon (TC TCInf) _ -> Just $ NTNat $ Inf''
TVar x | kindOf x == KNum -> Just $ NTVar x
_ -> Nothing
fromNumType :: OrdTerms -> NumType -> Maybe Type
fromNumType _ (NTVar x) = Just (TVar x)
fromNumType _ (NTNat Inf'') = Just tInf
fromNumType _ (NTNat FinNat'') = Nothing
fromNumType _ (NTNat (Nat'' x)) = Just (tNum x)
fromNumType ts (NTNamed x) = IntMap.lookup x (nameToType ts)
isVar :: NumType -> Bool
isVar (NTVar _) = True
isVar (NTNamed _) = True
isVar (NTNat _) = False
noFacts :: OrdFacts
noFacts = snd $ insNode inf
$ snd $ insNode fin
$ snd $ insNode zero
$ OrdFacts Map.empty OrdTerms { typeToName = emptyTM
, nameToType = IntMap.empty
, nextId = 0
}
noEdges :: Edges
noEdges = Edges { above = Set.empty, below = Set.empty }
-- | Get the edges immediately above or bellow a node.
imm :: (Edges -> Set Edge) -> OrdFacts -> NumType -> Set Edge
imm dir (OrdFacts m _) t = maybe Set.empty dir (Map.lookup t m)
-- Try to find a path from one node to another.
reachable :: OrdFacts -> NumType -> NumType -> Bool
reachable m smaller larger =
search Set.empty (Set.singleton Edge { target = smaller, eSource = NoGoal })
where
search visited todo
| Just (Edge { target = term }, rest) <- Set.minView todo =
if term == larger
then True
else let new = imm above m term
vis = Set.insert term visited
notDone = Set.filter (not . (`Set.member` vis) . target)
in search vis (notDone new `Set.union` notDone rest)
| otherwise = False
{-
The linking function is a bit complex because we keep the ordering database
minimal.
This diagram illustrates what we do when we link two nodes (`link`).
We start with a situation like on the left, and we are adding an
edge from L to U. The final result is illustrated on the right.
Before After
a a
/| /
/ | /
U | U\
| L \L
| / /
|/ /
d d
L: lower
U: upper
a: a member of "above uedges" (uus)
d: a member of "below ledges" (lls)
-}
{- XXX: It would be useful to return the edges that were removed because these
edges can be solved in term of the existing facts, so if some of them correspond
to wanted constrainst we can discharge them straight aways. We still get
the same effect in `reExamineWanteds` but in a much less effecieant way. -}
link :: EdgeSrc -> (NumType,Edges) -> (NumType,Edges)
-> OrdFacts -> (Edges,Edges,OrdFacts)
link src (lower, ledges) (upper, uedges) m0 =
let uus = Set.mapMonotonic target (above uedges)
lls = Set.mapMonotonic target (below ledges)
rm x = Set.filter (not . (`Set.member` x) . target)
{- As soon as we insert someghing above a node, we remove any
links to `Inf''` because, inductively, the thing above will
already be connected -}
newLedges = ledges { above = Set.insert Edge { target = upper
, eSource = src }
$ rm (Set.insert inf uus) -- see comment
$ above ledges
}
newUedges = uedges { below = Set.insert Edge { target = lower
, eSource = src }
$ rm lls
$ below uedges
}
del x = Set.delete Edge { target = x, eSource = NoGoal }
adjust f t (OrdFacts m xs) = OrdFacts (Map.adjust f t m) xs
insert k x (OrdFacts m xs) = OrdFacts (Map.insert k x m) xs
adjAbove = adjust (\e -> e { above = del upper (above e) })
adjBelow = adjust (\e -> e { below = del lower (below e) })
fold f xs x = Set.fold f x xs
in ( newLedges
, newUedges
, insert lower newLedges
$ insert upper newUedges
$ fold adjAbove lls
$ fold adjBelow uus
m0
)
-- | Insert a new node in a collection of facts.
-- Returns the edges surrounding the new node.
-- * Variable nodes are always linked to 0 and Inf'' (directly or indirectly).
-- * Constant nodes are always linked to neighbouring constant nodes.
insNode :: NumType -> OrdFacts -> (Edges, OrdFacts)
insNode t model@(OrdFacts m0 xs) =
case Map.splitLookup t m0 of
(_, Just r, _) -> (r, model)
(left, Nothing, right) ->
let m1 = OrdFacts (Map.insert t noEdges m0) xs
in if isVar t
-- New variabeles are always linkes to 0 and inf.
then
case Map.lookup zero m0 of
Just zes ->
let (_,es1,m2@(OrdFacts m2M _)) = link NoGoal (zero,zes) (t,noEdges) m1
in case Map.lookup inf m2M of
Just ies ->
let (es2,_,m3) = link NoGoal (t,es1) (inf,ies) m2
in (es2,m3)
Nothing -> panic "insNode"
[ "infinity is missing from the model"
, show m0
]
Nothing -> panic "insNode"
[ "0 is missing from the model"
, show m0
]
-- Constants are linked to their neighbours.
else
-- link to a smaller constnat, if any
let ans2@(es2, m2) =
case toNum Map.findMax left of
Nothing -> (noEdges,m1)
Just l ->
let (_,x,y) = link NoGoal l (t,noEdges) m1
in (x,y)
-- link to a larger constant, if any
in case toNum Map.findMin right of
Nothing -> ans2
Just u ->
let (x,_,y) = link NoGoal (t,es2) u m2
in (x,y)
where
toNum f x = do guard (not (Map.null x))
let it@(n,_) = f x
guard (not (isVar n))
return it
isLeq :: OrdFacts -> NumType -> NumType -> Bool
isLeq m t1 t2 = reachable m2 t1 t2
where (_,m1) = insNode t1 m
(_,m2) = insNode t2 m1
insertLeq :: EdgeSrc -> NumType -> NumType -> OrdFacts -> AssertResult
insertLeq _ (NTNat Inf'') (NTNat Inf'') _ = OrdAlreadyKnown
insertLeq _ (NTNat Inf'') (NTNat FinNat'') _ = OrdImpossible
insertLeq _ (NTNat Inf'') (NTNat (Nat'' _)) _ = OrdImpossible
insertLeq _ (NTNat FinNat'') (NTNat Inf'') _ = OrdAlreadyKnown
insertLeq _ (NTNat FinNat'') (NTNat FinNat'') _ = OrdAlreadyKnown -- can't happen
insertLeq _ (NTNat FinNat'') (NTNat (Nat'' _)) _= OrdImpossible -- ditto
insertLeq _ (NTNat (Nat'' _)) (NTNat Inf'') _ = OrdAlreadyKnown
insertLeq _ (NTNat (Nat'' _)) (NTNat FinNat'') _ = OrdAlreadyKnown
insertLeq _ (NTNat (Nat'' x)) (NTNat (Nat'' y)) _
| x <= y = OrdAlreadyKnown
| otherwise = OrdImpossible
insertLeq src t1 t2 m0
| reachable m2 t2 t1 = case (fromNumType terms t1, fromNumType terms t2) of
(Just a, Just b) -> OrdImprove a b
_ -> OrdCannot -- should not happen
| otherwise =
if reachable m2 t1 t2
then OrdAlreadyKnown
else let (_,_,m3) = link src (t1,n1) (t2,n2) m2
in OrdAdded m3
where (_,m1) = insNode t1 m0
(n2,m2@(OrdFacts m2M terms)) = insNode t2 m1
Just n1 = Map.lookup t1 m2M
insertFin :: EdgeSrc -> NumType -> OrdFacts -> AssertResult
insertFin src t m = insertLeq src t (NTNat FinNat'') m
getLowerBound :: OrdFacts -> NumType -> Nat''
getLowerBound _ (NTNat n) = n
getLowerBound fs@(OrdFacts m _) t =
case Map.lookup t m of
Nothing -> Nat'' 0
Just es -> case map (getLowerBound fs . target) $ Set.toList $ below es of
[] -> Nat'' 0
xs -> maximum xs
getUpperBound :: OrdFacts -> NumType -> Nat''
getUpperBound _ (NTNat n) = n
getUpperBound fs@(OrdFacts m _) t =
case Map.lookup t m of
Nothing -> Inf''
Just es -> case map (getUpperBound fs . target) $ Set.toList $ above es of
[] -> Inf''
xs -> minimum xs
--------------------------------------------------------------------------------
-- Testing
-- | Render facts in `dot` notation. The boolean says if we want the arrows
-- to point up.
dumpDot :: Bool -> OrdFacts -> String
dumpDot isUp (OrdFacts m _) = "digraph {" ++ concatMap edges (Map.toList m) ++ "}"
where
edge x e = x ++ " -> " ++ node (target e) ++ "[color=\"blue\"];"
dir = if isUp then above else below
edges (x,es) = let n = node x
in n ++ ";" ++
concatMap (edge n) (Set.toList (dir es))
node (NTNat (Nat'' x)) = show (show x)
node (NTNat FinNat'') = show "fin"
node (NTNat Inf'') = show "inf"
node (NTVar (TVFree x _ _ _)) = show ("?v" ++ show x)
node (NTVar (TVBound x _)) = show ("v" ++ show x)
node (NTNamed x) = show ("<" ++ show x ++ ">")
dumpDoc :: OrdFacts -> Doc
dumpDoc = vcat . ordFactsToList mk
where
mk src = Just $ \x -> case src of
NoGoal -> text "[G]" <+> pp x
FromGoal {} -> text "[W]" <+> pp x