-- | -- Module : $Header$ -- Copyright : (c) 2013-2015 Galois, Inc. -- License : BSD3 -- Maintainer : cryptol@galois.com -- Stability : provisional -- Portability : portable -- -- Solver code that does not depend on the type inference monad. {-# LANGUAGE PatternGuards #-} {-# LANGUAGE Safe #-} module Cryptol.TypeCheck.Solver.Numeric ( numericStep , simpFin , goalOrderModel ) where import Cryptol.Utils.Panic(panic) import Cryptol.TypeCheck.AST import Cryptol.TypeCheck.Unify(mgu,Result(..)) import Cryptol.TypeCheck.Subst(fvs) import Cryptol.TypeCheck.InferTypes(Goal(..), Solved(..)) import Cryptol.TypeCheck.Solver.FinOrd(OrdFacts, AssertResult(..),addFact) import Cryptol.TypeCheck.Solver.Interval(Interval(..),iDisjoint) import Cryptol.TypeCheck.Solver.Eval(typeInterval,simpType, typeKnownFin,typeKnownLeq, assumedOrderModel, derivedOrd ) import Cryptol.TypeCheck.Solver.InfNat(Nat'(..)) import Cryptol.TypeCheck.Solver.Utils import Control.Monad(guard,msum) import qualified Data.Set as Set -- | Try to perform a single step of simplification for a -- numeric goal. We assume that the substitution has been applied to the -- goal. numericStep :: OrdFacts -> Goal -> Solved numericStep m g = case tNoUser (simpType m (goal g)) of -- First we check if things are exactly the same, or we can -- substitutie in a variable (e.g., ?a = 1 + x) TCon (PC PEqual) [t1,t2] | OK (su,[]) <- unifier -> solvedS (Just su) [] | Error _ <- unifier -> Unsolvable where unifier = mgu t1 t2 TCon (PC PEqual) [t1,t2] -- Check if Inf is the only possible solution | Just prop <- checkOnlyInf m t1 t2 -> solved [prop] -- k1 + t1 = k2 + t2 | Just (k1,t1') <- splitConstSummand t1 , Just (k2,t2') <- splitConstSummand t2 -> let sub = case compare k1 k2 of EQ -> t1' =#= t2' LT -> t1' =#= (tNum (k2 - k1) .+. t2') GT -> (tNum (k1 - k2) .+. t1') =#= t2' in solved [sub] -- fin a => a + t1 = a + t2 | Just p <- eqByCancel m t1 t2 -> solved [p] -- k1 * t1 = k2 * t2 | Just (k1,t1') <- splitConstFactor t1 , Just (k2,t2') <- splitConstFactor t2 , let c = gcd k1 k2 , c > 1 -> solved [ tNum (div k1 c) .*. t1' =#= tNum (div k2 c) .*. t2' ] -- (x < b, x = min a b) => (x = a) | TCon (TF TCMin) [a,b] <- t1 , Just ps <- simpEqMin m t2 a b -> solved ps | TCon (TF TCMin) [a,b] <- t2 , Just ps <- simpEqMin m t1 a b -> solved ps -- (k >= 1, a = min (a + k, t)) => a = t | TVar a <- tNoUser t1 , Just ps <- aIsMin m a (splitMins t2) -> solved ps | TVar a <- tNoUser t2 , Just ps <- aIsMin m a (splitMins t1) -> solved ps -- (?x + s = t, s <= t, fin s) => (?x = t - s) -- useful as long as `?x` not in `fvs (s,t)` | Just ps <- eqBySub m t1 t2 -> solved ps -- Impossible | iDisjoint i1 i2 -> Unsolvable where i1 = typeInterval m t1 i2 = typeInterval m t2 -- We know these are not simple because they would have ended up -- in the OrdFacts TCon (PC PGeq) [t1,t2] | typeKnownLeq m t2 t1 -> solved [] | otherwise -> Unsolved prop | Just ps <- simpFin m prop -> solved ps _ -> Unsolved where solved = solvedS Nothing solvedS su xs = Solved su [ g { goal = x } | x <- xs ] -- t1 == min t2 t3 simpEqMin :: OrdFacts -> Type -> Type -> Type -> Maybe [Prop] simpEqMin m t1 t2 t3 | t1 == t2 = Just [ t3 >== t1 ] -- t1 = min t1 t3 | t1 == t3 = Just [ t2 >== t1 ] -- t1 = min t2 t1 | knownSmaller m t1 t2 = Just [ t1 =#= t3 ] | knownSmaller m t1 t3 = Just [ t1 =#= t2 ] | otherwise = Nothing -- | Check to see if we know that `t1` is strictly smaller than `t2` -- XXX: It'd be nice to combine this with knownLeq -- XXX: There can be all kinds of rules here. knownSmaller :: OrdFacts -> Type -> Type -> Bool knownSmaller m t1 t2 -- just a simple common case, arising from things like ([0] # something) | Just (_,t2') <- splitConstSummand t2 , isFinite (typeInterval m t1) , t1 == t2' = True knownSmaller _ _ _ = False simpFin :: OrdFacts -> Prop -> Maybe [Prop] simpFin m prop = case tNoUser prop of TCon (PC PFin) [ty] -> simpFinTy m ty _ -> Nothing simpFinTy :: OrdFacts -> Type -> Maybe [Prop] simpFinTy _ ty | TCon (TC (TCNum _)) _ <- tNoUser ty = Just [] simpFinTy m ty | typeKnownFin m ty = Just [] simpFinTy m ty = case tNoUser ty of TCon (TF tf) [t1] | TCLg2 <- tf -> Just [pFin t1] | TCWidth <- tf -> Just [pFin t1] TCon (TF tf) [t1,t2] | TCAdd <- tf -> Just [pFin t1, pFin t2] | TCSub <- tf -> Just [pFin t1] | TCMul <- tf , Nat n1 <- lowerBound i1, n1 >= 1 , Nat n2 <- lowerBound i2, n2 >= 1 -> Just [pFin t1, pFin t2] | TCDiv <- tf -> Just [pFin t1] | TCMod <- tf -> Just [] -- hm | TCExp <- tf -> Just [pFin t1, pFin t2] | TCMin <- tf -> Nothing | TCMax <- tf -> Just [pFin t1, pFin t2] where i1 = typeInterval m t1 i2 = typeInterval m t2 TCon (TF tf) [_,_,_] | TCLenFromThen <- tf -> Just [] | TCLenFromThenTo <- tf -> Just [] _ -> Nothing {- | Detect equations of the form: a = p + a // fin p, p >= 1 inf = p + a // fin p inf = p * a // fin p, p >= 1 The only solution to such equations is when `a = inf`, which is what we return. When in doubt, it is OK to return `Nothing` -} checkOnlyInf :: OrdFacts -> Type -> Type -> Maybe Prop checkOnlyInf ordM t1 t2 = case (tNoUser t1, tNoUser t2) of (TCon (TC TCInf) _, ty) -> checkInf ty (ty, TCon (TC TCInf) _) -> checkInf ty (TVar x, ty) -> checkVar x ty (ty, TVar x) -> checkVar x ty (_,_) -> Nothing where checkVar a ty = do ty1 <- splitVarSummand a ty guard (validP 1 ty1) return (TVar a =#= tInf) validP lb p = let i = typeInterval ordM p in isFinite i && lowerBound i >= Nat lb checkInf ty = case ty of TCon (TF TCAdd) [l,r] | validP 0 l -> Just (r =#= tInf) | validP 0 r -> Just (l =#= tInf) TCon (TF TCMul) [l,r] | validP 1 l -> Just (r =#= tInf) | validP 1 r -> Just (l =#= tInf) _ -> Nothing -- (?x + s = t, fin s) <=> (t >= s, ?x = t - s) -- useful as long as `?x` not in `fvs (s,t)` eqBySub :: OrdFacts -> Type -> Type -> Maybe [Prop] eqBySub ordM t1 t2 = msum $ zipWith attempt (splitVarSummands t1) (repeat t2) ++ zipWith attempt (splitVarSummands t2) (repeat t1) where attempt (x,s) t | not (x `Set.member` fvs (s,t)) && typeKnownFin ordM s = Just [ TVar x =#= (t .-. s), t >== s ] | otherwise = Nothing -- (fin a, a + x == a + y) => (x == y) eqByCancel :: OrdFacts -> Type -> Type -> Maybe Prop eqByCancel ordM t1 t2 = msum [ check x | x <- Set.toList (fvs t1), typeKnownFin ordM (TVar x) ] where check x = do t1' <- splitVarSummand x t1 t2' <- splitVarSummand x t2 return (t1' =#= t2') -- (a == min (k + a, t), k >= 1) => a == t aIsMin :: OrdFacts -> TVar -> [Type] -> Maybe [Prop] aIsMin _ _ [] = Nothing aIsMin _ _ [_] = Nothing aIsMin m a ts0 = attempt [] ts0 where tMins [] = panic "Cryptol.TypeCheck.Solver.Numeric" [ "tMins []" ] tMins [t] = t tMins ts = foldr1 tMin ts attempt _ [] = Nothing attempt others (t:ts) | isAparat m a t = Just [ TVar a =#= tMins (ts ++ others) ] | otherwise = attempt (t:others) ts -- Either this or splitVars summand could be fancier and go through functions isAparat :: OrdFacts -> TVar -> Type -> Bool isAparat m x ty = case splitVarSummand x ty of Just t1 -> typeKnownLeq m (tNum (1::Int)) t1 _ -> False -- | Collect `fin` and `<=` constraints in the ord model -- Returns (new model, bad goals, other goals). -- "bad goals" are goals that are incompatible with the model -- "other goals" are ones that are not "<=" or "fin" goalOrderModel :: OrdFacts -> [Goal] -> (OrdFacts, [Goal], [Goal]) goalOrderModel m0 todo = go m0 [] [] False [ g { goal = simpType m0 (goal g) } | g <- todo ] where go m bad others changes [] | changes = let (m1,newBad,newOthers) = goalOrderModel m others in (m1, newBad ++ bad, newOthers) | otherwise = case concatMap (derivedOrd m) (map goal others) of [] -> (m,bad,others) der -> case assumedOrderModel m der of -- we know that these goals cannot be solved -- but we don't have a good way to report the err -- For now, we just leave the goals alone Left _err -> (m,bad,others) Right (m1,_) -> (m1,bad,others) go m bad others changes (g : gs) | Just ps <- simpFin m (goal g) = go m bad others changes ([ g { goal = p } | p <- ps ] ++ gs) go m bad others changes (g : gs) = case addFact g m of OrdAlreadyKnown -> go m bad others changes gs OrdAdded m1 -> go m1 bad others True gs OrdCannot -> go m bad (g : others) changes gs OrdImprove t1 t2 -> go m bad (g { goal = t1 =#= t2 } : others) changes gs OrdImpossible -> go m (g : bad) others changes gs