mirror of
https://github.com/GaloisInc/cryptol.git
synced 2024-11-24 06:52:44 +03:00
Merge branch 'master' into logic-class
# Conflicts: # tests/mono-binds/test05.icry.stdout
This commit is contained in:
commit
1ed5640339
@ -76,6 +76,7 @@ library
|
||||
mtl >= 2.2.1,
|
||||
time >= 1.6.0.1
|
||||
|
||||
|
||||
Build-tools: alex, happy
|
||||
hs-source-dirs: src
|
||||
|
||||
@ -140,16 +141,8 @@ library
|
||||
|
||||
Cryptol.TypeCheck.Solver.Numeric,
|
||||
Cryptol.TypeCheck.Solver.Improve,
|
||||
Cryptol.TypeCheck.Solver.CrySAT,
|
||||
Cryptol.TypeCheck.Solver.Numeric.AST,
|
||||
Cryptol.TypeCheck.Solver.Numeric.ImportExport,
|
||||
Cryptol.TypeCheck.Solver.Numeric.Defined,
|
||||
Cryptol.TypeCheck.Solver.Numeric.Fin,
|
||||
Cryptol.TypeCheck.Solver.Numeric.Interval,
|
||||
Cryptol.TypeCheck.Solver.Numeric.Simplify,
|
||||
Cryptol.TypeCheck.Solver.Numeric.SimplifyExpr,
|
||||
Cryptol.TypeCheck.Solver.Numeric.NonLin,
|
||||
Cryptol.TypeCheck.Solver.Numeric.SMT,
|
||||
|
||||
Cryptol.Transform.MonoValues,
|
||||
Cryptol.Transform.Specialize,
|
||||
|
@ -84,37 +84,40 @@ initialModuleEnv = do
|
||||
let handle :: X.IOException -> IO String
|
||||
handle _e = return ""
|
||||
userDir <- X.catch (getAppUserDataDirectory "cryptol") handle
|
||||
let searchPath = [ curDir
|
||||
-- something like $HOME/.cryptol
|
||||
, userDir
|
||||
#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
|
||||
-- ../cryptol on win32
|
||||
, instDir </> "cryptol"
|
||||
#else
|
||||
-- ../share/cryptol on others
|
||||
, instDir </> "share" </> "cryptol"
|
||||
#endif
|
||||
|
||||
#ifndef RELOCATABLE
|
||||
-- Cabal-defined data directory. Since this
|
||||
-- is usually a global location like
|
||||
-- /usr/local, search this one last in case
|
||||
-- someone has multiple Cryptols
|
||||
, dataDir
|
||||
#endif
|
||||
]
|
||||
|
||||
return ModuleEnv
|
||||
{ meLoadedModules = mempty
|
||||
, meNameSeeds = T.nameSeeds
|
||||
, meEvalEnv = mempty
|
||||
, meFocusedModule = Nothing
|
||||
-- we search these in order, taking the first match
|
||||
, meSearchPath = [ curDir
|
||||
-- something like $HOME/.cryptol
|
||||
, userDir
|
||||
#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
|
||||
-- ../cryptol on win32
|
||||
, instDir </> "cryptol"
|
||||
#else
|
||||
-- ../share/cryptol on others
|
||||
, instDir </> "share" </> "cryptol"
|
||||
#endif
|
||||
|
||||
#ifndef RELOCATABLE
|
||||
-- Cabal-defined data directory. Since this
|
||||
-- is usually a global location like
|
||||
-- /usr/local, search this one last in case
|
||||
-- someone has multiple Cryptols
|
||||
, dataDir
|
||||
#endif
|
||||
]
|
||||
, meSearchPath = searchPath
|
||||
, meDynEnv = mempty
|
||||
, meMonoBinds = True
|
||||
, meSolverConfig = T.SolverConfig
|
||||
{ T.solverPath = "z3"
|
||||
, T.solverArgs = [ "-smt2", "-in" ]
|
||||
, T.solverVerbose = 0
|
||||
, T.solverPreludePath = searchPath
|
||||
}
|
||||
, meCoreLint = NoCoreLint
|
||||
, meSupply = emptySupply
|
||||
|
@ -16,7 +16,7 @@
|
||||
module Cryptol.Prelude (
|
||||
writePreludeContents,
|
||||
writePreludeExtrasContents,
|
||||
writeTcPreludeContents,
|
||||
cryptolTcContents
|
||||
) where
|
||||
|
||||
|
||||
@ -51,12 +51,4 @@ writePreludeExtrasContents = do
|
||||
cryptolTcContents :: String
|
||||
cryptolTcContents = [there|lib/CryptolTC.z3|]
|
||||
|
||||
-- | Write the contents of the Prelude to a temporary file so that
|
||||
-- Cryptol can load the module.
|
||||
writeTcPreludeContents :: IO FilePath
|
||||
writeTcPreludeContents = do
|
||||
tmpdir <- getTemporaryDirectory
|
||||
(path, h) <- openTempFile tmpdir "CryptolTC.z3"
|
||||
hPutStr h cryptolTcContents
|
||||
hClose h
|
||||
return path
|
||||
|
||||
|
@ -70,7 +70,7 @@ import qualified Cryptol.TypeCheck.Parseable as T
|
||||
import qualified Cryptol.TypeCheck.Subst as T
|
||||
import qualified Cryptol.TypeCheck.InferTypes as T
|
||||
import Cryptol.TypeCheck.Solve(defaultReplExpr)
|
||||
import qualified Cryptol.TypeCheck.Solver.CrySAT as CrySAT
|
||||
import qualified Cryptol.TypeCheck.Solver.SMT as SMT
|
||||
import Cryptol.TypeCheck.PP (dump,ppWithNames)
|
||||
import Cryptol.Utils.PP
|
||||
import Cryptol.Utils.Panic(panic)
|
||||
@ -964,7 +964,7 @@ replEvalExpr expr =
|
||||
|
||||
me <- getModuleEnv
|
||||
let cfg = M.meSolverConfig me
|
||||
mbDef <- io $ CrySAT.withSolver cfg (\s -> defaultReplExpr s def sig)
|
||||
mbDef <- io $ SMT.withSolver cfg (\s -> defaultReplExpr s def sig)
|
||||
|
||||
(def1,ty) <-
|
||||
case mbDef of
|
||||
|
@ -27,7 +27,7 @@ import Cryptol.TypeCheck.Kind(checkType,checkSchema,checkTySyn,
|
||||
checkPropSyn,checkNewtype)
|
||||
import Cryptol.TypeCheck.Instantiate
|
||||
import Cryptol.TypeCheck.Depends
|
||||
import Cryptol.TypeCheck.Subst (listSubst,apSubst,(@@),emptySubst)
|
||||
import Cryptol.TypeCheck.Subst (listSubst,apSubst,(@@))
|
||||
import Cryptol.TypeCheck.Solver.InfNat(genLog)
|
||||
import Cryptol.Utils.Ident
|
||||
import Cryptol.Utils.Panic(panic)
|
||||
@ -686,15 +686,7 @@ generalize bs0 gs0 =
|
||||
when (not (null ambig)) $ recordError $ AmbiguousType $ map dName bs
|
||||
|
||||
|
||||
solver <- getSolver
|
||||
(as0,here1,mb_defSu,ws) <- io $ improveByDefaultingWith solver maybeAmbig here0
|
||||
defSu <- case mb_defSu of
|
||||
Nothing -> do recordError $ UnsolvedGoals True here0
|
||||
return emptySubst
|
||||
Just s -> return s
|
||||
|
||||
|
||||
|
||||
let (as0,here1,defSu,ws) = improveByDefaultingWithPure maybeAmbig here0
|
||||
mapM_ recordWarning ws
|
||||
let here = map goal here1
|
||||
|
||||
@ -704,7 +696,7 @@ generalize bs0 gs0 =
|
||||
totSu <- getSubst
|
||||
let
|
||||
su = listSubst (zip as (map (TVar . tpVar) asPs)) @@ defSu @@ totSu
|
||||
qs = map (apSubst su) here
|
||||
qs = concatMap (pSplitAnd . apSubst su) here
|
||||
|
||||
genE e = foldr ETAbs (foldr EProofAbs (apSubst su e) qs) asPs
|
||||
genB d = d { dDefinition = case dDefinition d of
|
||||
@ -798,17 +790,12 @@ checkSigB b (Forall as asmps0 t0, validSchema) = case thing (P.bDef b) of
|
||||
when (not (null ambig)) $ recordError
|
||||
$ AmbiguousType [ thing (P.bName b) ]
|
||||
|
||||
solver <- getSolver
|
||||
(_,_,mb_defSu2,ws) <-
|
||||
io $ improveByDefaultingWith solver maybeAmbig later
|
||||
defSu2 <- case mb_defSu2 of
|
||||
Nothing -> do recordError $ UnsolvedGoals True later
|
||||
return emptySubst
|
||||
Just s -> return s
|
||||
-- XXX: Uhm, why are we defaulting that 'later' things here?
|
||||
-- Surely this should be done later, when we solve them?
|
||||
let (_,newGs,defSu2,ws) = improveByDefaultingWithPure maybeAmbig later
|
||||
mapM_ recordWarning ws
|
||||
extendSubst defSu2
|
||||
|
||||
addGoals later
|
||||
addGoals newGs
|
||||
|
||||
su <- getSubst
|
||||
let su' = defSu1 @@ su
|
||||
|
@ -41,6 +41,8 @@ data SolverConfig = SolverConfig
|
||||
{ solverPath :: FilePath -- ^ The SMT solver to invoke
|
||||
, solverArgs :: [String] -- ^ Additional arguments to pass to the solver
|
||||
, solverVerbose :: Int -- ^ How verbose to be when type-checking
|
||||
, solverPreludePath :: [FilePath]
|
||||
-- ^ Look for the solver prelude in these locations.
|
||||
} deriving (Show, Generic, NFData)
|
||||
|
||||
-- | The types of variables in the environment.
|
||||
|
@ -19,13 +19,12 @@ module Cryptol.TypeCheck.Monad
|
||||
import Cryptol.ModuleSystem.Name (FreshM(..),Supply)
|
||||
import Cryptol.Parser.Position
|
||||
import qualified Cryptol.Parser.AST as P
|
||||
import Cryptol.Prelude (writeTcPreludeContents)
|
||||
import Cryptol.TypeCheck.AST
|
||||
import Cryptol.TypeCheck.Subst
|
||||
import Cryptol.TypeCheck.Unify(mgu, Result(..), UnificationError(..))
|
||||
import Cryptol.TypeCheck.InferTypes
|
||||
import qualified Cryptol.TypeCheck.SimpleSolver as Simple
|
||||
import qualified Cryptol.TypeCheck.Solver.CrySAT as CrySAT
|
||||
import qualified Cryptol.TypeCheck.Solver.SMT as SMT
|
||||
import Cryptol.Utils.PP(pp, (<+>), Doc, text, quotes)
|
||||
import Cryptol.Utils.Panic(panic)
|
||||
|
||||
@ -42,8 +41,6 @@ import MonadLib hiding (mapM)
|
||||
|
||||
import Data.IORef
|
||||
|
||||
import System.FilePath((</>))
|
||||
import System.Directory(doesFileExist)
|
||||
|
||||
|
||||
import GHC.Generics (Generic)
|
||||
@ -102,9 +99,8 @@ bumpCounter = do RO { .. } <- IM ask
|
||||
io $ modifyIORef' iSolveCounter (+1)
|
||||
|
||||
runInferM :: TVars a => InferInput -> InferM a -> IO (InferOutput a)
|
||||
runInferM info (IM m) = CrySAT.withSolver (inpSolverConfig info) $ \solver ->
|
||||
do loadCryTCPrel solver (inpSearchPath info)
|
||||
coutner <- newIORef 0
|
||||
runInferM info (IM m) = SMT.withSolver (inpSolverConfig info) $ \solver ->
|
||||
do coutner <- newIORef 0
|
||||
rec ro <- return RO { iRange = inpRange info
|
||||
, iVars = Map.map ExtVar (inpVars info)
|
||||
, iTVars = []
|
||||
@ -168,15 +164,6 @@ runInferM info (IM m) = CrySAT.withSolver (inpSolverConfig info) $ \solver ->
|
||||
-- The actual order does not matter
|
||||
cmpRange (Range x y z) (Range a b c) = compare (x,y,z) (a,b,c)
|
||||
|
||||
loadCryTCPrel s [] =
|
||||
do file <- writeTcPreludeContents
|
||||
CrySAT.loadFile s file
|
||||
|
||||
loadCryTCPrel s (p : ps) =
|
||||
do let file = p </> "CryptolTC.z3"
|
||||
yes <- doesFileExist file
|
||||
if yes then CrySAT.loadFile s file
|
||||
else loadCryTCPrel s ps
|
||||
|
||||
|
||||
|
||||
@ -218,7 +205,7 @@ data RO = RO
|
||||
-- in where-blocks will never be generalized. Bindings with type
|
||||
-- signatures, and all bindings at top level are unaffected.
|
||||
|
||||
, iSolver :: CrySAT.Solver
|
||||
, iSolver :: SMT.Solver
|
||||
|
||||
, iPrimNames :: !PrimMap
|
||||
|
||||
@ -309,7 +296,7 @@ recordWarning w =
|
||||
do r <- curRange
|
||||
IM $ sets_ $ \s -> s { iWarnings = (r,w) : iWarnings s }
|
||||
|
||||
getSolver :: InferM CrySAT.Solver
|
||||
getSolver :: InferM SMT.Solver
|
||||
getSolver =
|
||||
do RO { .. } <- IM ask
|
||||
return iSolver
|
||||
|
@ -13,7 +13,7 @@ module Cryptol.TypeCheck.Solve
|
||||
, proveImplication
|
||||
, wfType
|
||||
, wfTypeFunction
|
||||
, improveByDefaultingWith
|
||||
, improveByDefaultingWithPure
|
||||
, defaultReplExpr
|
||||
) where
|
||||
|
||||
@ -23,34 +23,28 @@ import Cryptol.TypeCheck.Monad
|
||||
import Cryptol.TypeCheck.Subst
|
||||
(apSubst, singleSubst, isEmptySubst, substToList,
|
||||
emptySubst,Subst,listSubst, (@@), Subst,
|
||||
apSubstMaybe, substBinds)
|
||||
substBinds)
|
||||
import qualified Cryptol.TypeCheck.SimpleSolver as Simplify
|
||||
import Cryptol.TypeCheck.Solver.Types
|
||||
import Cryptol.TypeCheck.Solver.Selector(tryHasGoal)
|
||||
import Cryptol.TypeCheck.SimpType(tMax)
|
||||
|
||||
|
||||
import Cryptol.TypeCheck.Solver.SMT(proveImp,checkUnsolvable)
|
||||
import Cryptol.TypeCheck.Solver.SMT(Solver,proveImp,tryGetModel)
|
||||
import Cryptol.TypeCheck.Solver.Improve(improveProp,improveProps)
|
||||
import Cryptol.TypeCheck.Solver.Numeric.Interval
|
||||
import qualified Cryptol.TypeCheck.Solver.Numeric.AST as Num
|
||||
import qualified Cryptol.TypeCheck.Solver.Numeric.ImportExport as Num
|
||||
import qualified Cryptol.TypeCheck.Solver.CrySAT as Num
|
||||
import Cryptol.TypeCheck.Solver.CrySAT
|
||||
import Cryptol.Utils.PP (text,vcat,(<+>))
|
||||
import Cryptol.Utils.Panic(panic)
|
||||
import Cryptol.Utils.Patterns(matchMaybe)
|
||||
|
||||
import Control.Monad (guard, mzero)
|
||||
import Control.Applicative ((<|>))
|
||||
import Data.Either(partitionEithers)
|
||||
import Data.Maybe(catMaybes)
|
||||
import Data.Map ( Map )
|
||||
import Control.Monad(mzero,guard)
|
||||
import qualified Data.Map as Map
|
||||
import Data.Set ( Set )
|
||||
import qualified Data.Set as Set
|
||||
|
||||
|
||||
|
||||
{- | Add additional constraints that ensure validity of type function.
|
||||
Note that these constraints do not introduce additional malformed types,
|
||||
so the well-formedness constraints are guaranteed to be well-formed.
|
||||
@ -185,7 +179,7 @@ proveImplication lnam as ps gs =
|
||||
return su
|
||||
|
||||
|
||||
proveImplicationIO :: Num.Solver
|
||||
proveImplicationIO :: Solver
|
||||
-> Name -- ^ Checking this function
|
||||
-> Set TVar -- ^ These appear in the env., and we should
|
||||
-- not try to default the
|
||||
@ -259,49 +253,6 @@ cleanupError err =
|
||||
|
||||
|
||||
|
||||
simpGoals' :: Num.Solver -> Ctxt -> [Goal] -> IO (Either [Goal] [Goal], Subst)
|
||||
simpGoals' s asmps gs0 = go emptySubst [] (wellFormed gs0 ++ gs0)
|
||||
where
|
||||
-- Assumes that the well-formed constraints are themselves well-formed.
|
||||
wellFormed gs = [ g { goal = p } | g <- gs, p <- wfType (goal g) ]
|
||||
|
||||
go su old [] = return (Right old, su)
|
||||
go su old gs =
|
||||
do res <- solveConstraints s asmps old gs
|
||||
case res of
|
||||
Left err -> return (Left err, su)
|
||||
Right gs2 ->
|
||||
do let gs3 = gs2 ++ old
|
||||
mb <- computeImprovements s gs3
|
||||
case mb of
|
||||
Left err -> return (Left err, su)
|
||||
Right impSu ->
|
||||
let (unchanged,changed) =
|
||||
partitionEithers (map (applyImp impSu) gs3)
|
||||
new = wellFormed changed
|
||||
in go (impSu @@ su) unchanged (new ++ changed)
|
||||
|
||||
applyImp su g = case apSubstMaybe su (goal g) of
|
||||
Nothing -> Left g
|
||||
Just p -> Right g { goal = p }
|
||||
|
||||
|
||||
{- Note:
|
||||
It is good to consider the other goals when evaluating terms.
|
||||
For example, consider the constraints:
|
||||
|
||||
P (x * inf), x >= 1
|
||||
|
||||
We cannot simplify `x * inf` on its own, because we do not know if `x`
|
||||
might be 0. However, in the contxt of `x >= 1`, we know that this is
|
||||
impossible, and we can simplify the constraints to:
|
||||
|
||||
P inf, x >= 1
|
||||
|
||||
However, we should be careful to avoid circular reasoning, as we wouldn't
|
||||
want to use the fact that `x >= 1` to simplify `x >= 1` to true.
|
||||
-}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -314,110 +265,6 @@ assumptionIntervals as ps =
|
||||
|
||||
|
||||
|
||||
solveConstraints :: Num.Solver ->
|
||||
Ctxt ->
|
||||
[Goal] {- We may use these, but don't try to solve,
|
||||
we already tried and failed. -} ->
|
||||
[Goal] {- Need to solve these -} ->
|
||||
IO (Either [Goal] [Goal])
|
||||
-- ^ Left: contradiciting goals,
|
||||
-- Right: goals that were not solved, or sub-goals
|
||||
-- for solved goals. Does not include "old"
|
||||
solveConstraints s asmps otherGs gs0 =
|
||||
debugBlock s "Solving constraints" $ go ctxt0 [] gs0
|
||||
|
||||
where
|
||||
ctxt0 = assumptionIntervals asmps (map goal otherGs)
|
||||
|
||||
|
||||
go _ unsolved [] =
|
||||
do let (cs,nums) = partitionEithers (map Num.numericRight unsolved)
|
||||
nums' <- solveNumerics s otherNumerics nums
|
||||
return (Right (cs ++ nums'))
|
||||
|
||||
go ctxt unsolved (g : gs) =
|
||||
case Simplify.simplifyStep ctxt (goal g) of
|
||||
Unsolvable _x -> return (Left [g]) -- maybe give error?
|
||||
Unsolved -> go ctxt (g : unsolved) gs
|
||||
SolvedIf subs ->
|
||||
let cvt x = g { goal = x }
|
||||
in go ctxt unsolved (map cvt subs ++ gs)
|
||||
|
||||
|
||||
otherNumerics = [ g | Right g <- map Num.numericRight otherGs ]
|
||||
|
||||
|
||||
|
||||
|
||||
solveNumerics :: Num.Solver ->
|
||||
[(Goal,Num.Prop)] {- ^ Consult these -} ->
|
||||
[(Goal,Num.Prop)] {- ^ Solve these -} ->
|
||||
IO [Goal]
|
||||
solveNumerics _ _ [] = return []
|
||||
solveNumerics s consultGs solveGs =
|
||||
Num.withScope s $
|
||||
do _ <- Num.assumeProps s (map (goal . fst) consultGs)
|
||||
Num.simplifyProps s (map Num.knownDefined solveGs)
|
||||
|
||||
|
||||
computeImprovements :: Num.Solver -> [Goal] -> IO (Either [Goal] Subst)
|
||||
computeImprovements s gs =
|
||||
debugBlock s "Computing improvements" $
|
||||
do let nums = [ g | Right g <- map Num.numericRight gs ]
|
||||
res <- Num.withScope s $
|
||||
do _ <- Num.assumeProps s (map (goal . fst) nums)
|
||||
mb <- Num.check s
|
||||
case mb of
|
||||
Nothing -> return Nothing
|
||||
Just (suish,_ps1) ->
|
||||
do let (su,_ps2) = importSplitImps suish
|
||||
-- Num.check has already checked that the intervals are sane,
|
||||
-- so we don't need to check for a broken interval here
|
||||
Right ints <- Num.getIntervals s
|
||||
return (Just (ints,su))
|
||||
case res of
|
||||
Just (_ints, su) -> return (Right su) -- ?
|
||||
{-
|
||||
| isEmptySubst su
|
||||
, (x,t) : _ <- mapMaybe (improveByDefn ints) gs ->
|
||||
do let su' = singleSubst x t
|
||||
debugLog s ("Improve by definition: " ++ show (pp su'))
|
||||
return (Right su')
|
||||
| otherwise -> return (Right su)
|
||||
-}
|
||||
Nothing ->
|
||||
do bad <- Num.minimizeContradictionSimpDef s
|
||||
(map Num.knownDefined nums)
|
||||
return (Left bad)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Import an improving substitutin (i.e., a bunch of equations)
|
||||
-- into a Cryptol substitution (which is idempotent).
|
||||
-- The substitution will contain only unification variables.
|
||||
-- "Improvements" on skolem variables become additional constraints.
|
||||
importSplitImps :: Map Num.Name Num.Expr -> (Subst, [Prop])
|
||||
importSplitImps = mk . partitionEithers . map imp . Map.toList
|
||||
where
|
||||
mk (uni,props) = (listSubst (catMaybes uni), props)
|
||||
|
||||
imp (x,e) = case (x, Num.importType e) of
|
||||
(Num.UserName tv, Just ty) ->
|
||||
case tv of
|
||||
TVFree {} -> Left (Just (tv,ty))
|
||||
TVBound {} -> Right (TVar tv =#= ty)
|
||||
|
||||
{- This may happen if we are working on an implication,
|
||||
and we have an improvement about a variable in the
|
||||
assumptions that is not in any og the goals.
|
||||
XXX: Perhaps, we should mark these variable, so we don't waste
|
||||
time to "improve" them. -}
|
||||
|
||||
_ -> Left Nothing
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -447,47 +294,6 @@ importSplitImps = mk . partitionEithers . map imp . Map.toList
|
||||
3. a substitution which indicates what got defaulted.
|
||||
-}
|
||||
|
||||
improveByDefaultingWith ::
|
||||
Num.Solver ->
|
||||
[TVar] -> -- candidates for defaulting
|
||||
[Goal] -> -- constraints
|
||||
IO ( [TVar] -- non-defaulted
|
||||
, [Goal] -- new constraints
|
||||
, Maybe Subst -- Nothing: improve to False
|
||||
-- Just: improvements from defaulting
|
||||
, [Warning] -- warnings about defaulting
|
||||
)
|
||||
-- XXX: Remove this
|
||||
-- improveByDefaultingWith s as gs = return (as,gs,emptySubst,[])
|
||||
improveByDefaultingWith s as gs =
|
||||
do bad <- checkUnsolvable s gs
|
||||
if bad
|
||||
then return (as, gs, Nothing, [])
|
||||
else tryImp
|
||||
|
||||
where
|
||||
tryImp =
|
||||
case improveByDefaultingWithPure as gs of
|
||||
(xs,gs',su,ws) ->
|
||||
do (res,su1) <- simpGoals' s Map.empty gs'
|
||||
case res of
|
||||
Left err ->
|
||||
panic "improveByDefaultingWith"
|
||||
$ [ "Defaulting resulted in unsolvable constraints."
|
||||
, "Before:"
|
||||
] ++ [ " " ++ show (pp (goal g)) | g <- gs ] ++
|
||||
[ "After:"
|
||||
] ++ [ " " ++ show (pp (goal g)) | g <- gs' ] ++
|
||||
[ "Contradiction:" ] ++
|
||||
[ " " ++ show (pp (goal g)) | g <- err ]
|
||||
Right gs'' ->
|
||||
do let su2 = su1 @@ su
|
||||
isDef x = x `Set.member` substBinds su2
|
||||
return ( filter (not . isDef) xs
|
||||
, gs''
|
||||
, Just su2
|
||||
, ws
|
||||
)
|
||||
|
||||
|
||||
improveByDefaultingWithPure :: [TVar] -> [Goal] ->
|
||||
@ -588,31 +394,19 @@ improveByDefaultingWithPure as ps =
|
||||
-- | Try to pick a reasonable instantiation for an expression, with
|
||||
-- the given type. This is useful when we do evaluation at the REPL.
|
||||
-- The resulting types should satisfy the constraints of the schema.
|
||||
defaultReplExpr :: Num.Solver -> Expr -> Schema
|
||||
defaultReplExpr :: Solver -> Expr -> Schema
|
||||
-> IO (Maybe ([(TParam,Type)], Expr))
|
||||
-- defaultReplExpr _ _ _ = return Nothing
|
||||
defaultReplExpr so e s =
|
||||
defaultReplExpr sol e s =
|
||||
if all (\v -> kindOf v == KNum) (sVars s)
|
||||
then do let params = map tpVar (sVars s)
|
||||
mbSubst <- tryGetModel so params (sProps s)
|
||||
case mbSubst of
|
||||
Just su ->
|
||||
do (res,su1) <- simpGoals' so Map.empty (map (makeGoal su) (sProps s))
|
||||
return $
|
||||
case res of
|
||||
Right [] | isEmptySubst su1 ->
|
||||
do tys <- mapM (bindParam su) params
|
||||
return (zip (sVars s) tys, appExpr tys)
|
||||
_ -> Nothing
|
||||
_ -> return Nothing
|
||||
|
||||
props = sProps s
|
||||
mbSubst <- tryGetModel sol params props
|
||||
return $ do su <- mbSubst
|
||||
guard (null (concatMap pSplitAnd (apSubst su props)))
|
||||
tys <- mapM (bindParam su) params
|
||||
return (zip (sVars s) tys, appExpr tys)
|
||||
else return Nothing
|
||||
where
|
||||
makeGoal su p = Goal { goalSource = error "goal source"
|
||||
, goalRange = error "goal range"
|
||||
, goal = apSubst su p
|
||||
}
|
||||
|
||||
bindParam su tp =
|
||||
do let ty = TVar tp
|
||||
ty' = apSubst su ty
|
||||
@ -623,15 +417,5 @@ defaultReplExpr so e s =
|
||||
|
||||
|
||||
|
||||
-- | Attempt to default the given constraints by asserting them in the SMT
|
||||
-- solver, and asking it for a model.
|
||||
tryGetModel ::
|
||||
Num.Solver ->
|
||||
[TVar] -> -- variables to try defaulting
|
||||
[Prop] -> -- constraints
|
||||
IO (Maybe Subst)
|
||||
tryGetModel s xs ps =
|
||||
-- We are only interested in finite instantiations
|
||||
Num.getModel s (map (pFin . TVar) xs ++ ps)
|
||||
|
||||
|
||||
|
@ -1,696 +0,0 @@
|
||||
-- |
|
||||
-- Module : $Header$
|
||||
-- Copyright : (c) 2013-2016 Galois, Inc.
|
||||
-- License : BSD3
|
||||
-- Maintainer : cryptol@galois.com
|
||||
-- Stability : provisional
|
||||
-- Portability : portable
|
||||
|
||||
{-# LANGUAGE Safe #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE PatternGuards #-}
|
||||
{-# LANGUAGE TypeSynonymInstances #-}
|
||||
module Cryptol.TypeCheck.Solver.CrySAT
|
||||
( withScope, withSolver
|
||||
, assumeProps, simplifyProps, getModel
|
||||
, check
|
||||
, Solver, logger, getIntervals
|
||||
, DefinedProp(..)
|
||||
, debugBlock
|
||||
, DebugLog(..)
|
||||
, knownDefined, numericRight
|
||||
, minimizeContradictionSimpDef
|
||||
, loadFile
|
||||
, rawSolver
|
||||
) where
|
||||
|
||||
import qualified Cryptol.TypeCheck.AST as Cry
|
||||
import Cryptol.TypeCheck.InferTypes(Goal(..), SolverConfig(..))
|
||||
import qualified Cryptol.TypeCheck.Subst as Cry
|
||||
|
||||
import Cryptol.TypeCheck.Solver.Types
|
||||
import Cryptol.TypeCheck.Solver.Numeric.AST
|
||||
import Cryptol.TypeCheck.Solver.Numeric.Fin
|
||||
import Cryptol.TypeCheck.Solver.Numeric.ImportExport
|
||||
import Cryptol.TypeCheck.Solver.Numeric.Interval
|
||||
import Cryptol.TypeCheck.Solver.Numeric.Defined
|
||||
import Cryptol.TypeCheck.Solver.Numeric.Simplify
|
||||
import Cryptol.TypeCheck.Solver.Numeric.NonLin
|
||||
import Cryptol.TypeCheck.Solver.Numeric.SMT
|
||||
import Cryptol.Utils.PP -- ( Doc )
|
||||
import Cryptol.Utils.Panic ( panic )
|
||||
|
||||
import MonadLib
|
||||
import Data.Maybe ( fromMaybe )
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
import Data.Char(isSpace)
|
||||
import Data.Foldable ( any, all )
|
||||
import qualified Data.Set as Set
|
||||
import Data.IORef ( IORef, newIORef, readIORef, modifyIORef',
|
||||
atomicModifyIORef' )
|
||||
import Prelude hiding (any,all)
|
||||
import qualified System.IO.Strict as StrictIO
|
||||
|
||||
import qualified SimpleSMT as SMT
|
||||
|
||||
|
||||
-- | We use this to remember what we simplified
|
||||
newtype SimpProp = SimpProp { unSimpProp :: Prop }
|
||||
|
||||
simpProp :: Prop -> SimpProp
|
||||
simpProp p = SimpProp (crySimplify p)
|
||||
|
||||
|
||||
class HasProp a where getProp :: a -> Cry.Prop
|
||||
instance HasProp Cry.Prop where getProp = id
|
||||
instance HasProp Goal where getProp = goal
|
||||
|
||||
|
||||
-- | 'dpSimpProp' and 'dpSimpExprProp' should be logically equivalent,
|
||||
-- to each other, and to whatever 'a' represents (usually 'a' is a 'Goal').
|
||||
data DefinedProp a = DefinedProp
|
||||
{ dpData :: a
|
||||
-- ^ Optional data to associate with prop.
|
||||
-- Often, the original `Goal` from which the prop was extracted.
|
||||
|
||||
, dpSimpProp :: SimpProp
|
||||
{- ^ Fully simplified: may mention ORs, and named non-linear terms.
|
||||
These are what we send to the prover, and we don't attempt to
|
||||
convert them back into Cryptol types. -}
|
||||
|
||||
, dpSimpExprProp :: Prop
|
||||
{- ^ A version of the proposition where just the expression terms
|
||||
have been simplified. These should not contain ORs or named non-linear
|
||||
terms because we want to import them back into Crytpol types. -}
|
||||
}
|
||||
|
||||
knownDefined :: (a,Prop) -> DefinedProp a
|
||||
knownDefined (a,p) = DefinedProp
|
||||
{ dpData = a, dpSimpProp = simpProp p, dpSimpExprProp = p }
|
||||
|
||||
|
||||
-- | Class goals go on the left, numeric goals go on the right.
|
||||
numericRight :: Goal -> Either Goal (Goal, Prop)
|
||||
numericRight g = case exportProp (goal g) of
|
||||
Just p -> Right (g, p)
|
||||
Nothing -> Left g
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Simplify a bunch of well-defined properties.
|
||||
-- * Eliminates properties that are implied by the rest.
|
||||
-- * Does not modify the set of assumptions.
|
||||
simplifyProps :: Solver -> [DefinedProp Goal] -> IO [Goal]
|
||||
simplifyProps s props =
|
||||
debugBlock s "Simplifying properties" $
|
||||
withScope s (go [] (eliminateSimpleGEQ props))
|
||||
where
|
||||
go survived [] = return survived
|
||||
|
||||
go survived (DefinedProp { dpSimpProp = SimpProp PTrue } : more) =
|
||||
go survived more
|
||||
|
||||
go survived (p : more) =
|
||||
case dpSimpProp p of
|
||||
SimpProp PTrue -> go survived more
|
||||
SimpProp p' ->
|
||||
do mbProved <- withScope s $
|
||||
do mapM_ (assert s) more
|
||||
e <- getIntervals s
|
||||
case e of
|
||||
Left _ -> return Nothing
|
||||
Right ints -> do b <- prove s p'
|
||||
return (Just (ints,b))
|
||||
case mbProved of
|
||||
Just (_,True) -> go survived more
|
||||
|
||||
Just (ints,False) ->
|
||||
debugLog s ("Using the fin solver:" ++ show (pp (goal (dpData p)))) >>
|
||||
case cryIsFin ints (goal (dpData p)) of
|
||||
SolvedIf ps' ->
|
||||
do debugLog s "solved"
|
||||
let gg = dpData p
|
||||
gs' = [ gg { goal = pr } | pr <- ps' ]
|
||||
let more' = [ knownDefined g | Right g <- map numericRight gs' ]
|
||||
go survived (more' ++ more)
|
||||
Unsolved ->
|
||||
do debugLog s "unsolved"
|
||||
assert s p
|
||||
go (dpData p : survived) more
|
||||
|
||||
x@(Unsolvable {}) ->
|
||||
do debugLog s (show (pp x))
|
||||
go (dpData p:survived) more
|
||||
|
||||
Nothing -> go (dpData p:survived) more
|
||||
|
||||
|
||||
{- | Simplify easy less-than-or-equal-to and equal-to goals.
|
||||
Those are common with long lists of literals, so we have special handling
|
||||
for them. In particular:
|
||||
|
||||
* Reduce goals of the form @(a >= k1, a >= k2, a >= k3, ...)@ to
|
||||
@a >= max (k1, k2, k3, ...)@, when all the k's are constant.
|
||||
|
||||
* Eliminate goals of the form @ki >= k2@, when @k2@ is leq than @k1@.
|
||||
|
||||
* Eliminate goals of the form @a >= 0@.
|
||||
|
||||
NOTE: This assumes that the goals are well-defined.
|
||||
-}
|
||||
eliminateSimpleGEQ :: [DefinedProp a] -> [DefinedProp a]
|
||||
eliminateSimpleGEQ = go Map.empty []
|
||||
where
|
||||
|
||||
go geqs other (g : rest) =
|
||||
case dpSimpExprProp g of
|
||||
K a :== K b
|
||||
| a == b -> go geqs other rest
|
||||
|
||||
_ :>= K (Nat 0) ->
|
||||
go geqs other rest
|
||||
|
||||
K (Nat k1) :>= K (Nat k2)
|
||||
| k1 >= k2 -> go geqs other rest
|
||||
|
||||
Var v :>= K (Nat k2) ->
|
||||
go (addUpperBound v (k2,g) geqs) other rest
|
||||
|
||||
_ -> go geqs (g:other) rest
|
||||
|
||||
go geqs other [] = [ g | (_,g) <- Map.elems geqs ] ++ other
|
||||
|
||||
-- add in a possible upper bound for var
|
||||
addUpperBound var g = Map.insertWith cmp var g
|
||||
where
|
||||
cmp a b | fst a > fst b = a
|
||||
| otherwise = b
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Add the given constraints as assumptions.
|
||||
-- * We assume that the constraints are well-defined.
|
||||
-- * Modifies the set of assumptions.
|
||||
assumeProps :: Solver -> [Cry.Prop] -> IO [SimpProp]
|
||||
assumeProps s props =
|
||||
do let ps = [ (p,p') | p <- props
|
||||
, Just p' <- [exportProp p] ]
|
||||
|
||||
let defPs = [ (p,cryDefinedProp p') | (p,p') <- ps ]
|
||||
|
||||
let simpProps = map knownDefined (defPs ++ ps)
|
||||
mapM_ (assert s) simpProps
|
||||
return (map dpSimpProp simpProps)
|
||||
-- XXX: Instead of asserting one at a time, perhaps we should
|
||||
-- assert a conjunction. That way, we could simplify the whole thing
|
||||
-- in one go, and would avoid having to assert 'true' many times.
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Given a list of propositions that together lead to a contradiction,
|
||||
-- find a sub-set that still leads to a contradiction (but is smaller).
|
||||
minimizeContradictionSimpDef :: HasProp a => Solver -> [DefinedProp a] -> IO [a]
|
||||
minimizeContradictionSimpDef s ps = start [] ps
|
||||
where
|
||||
start bad todo =
|
||||
do res <- SMT.check (solver s)
|
||||
case res of
|
||||
SMT.Unsat -> return (map dpData bad)
|
||||
_ -> do solPush s
|
||||
go bad [] todo
|
||||
|
||||
go _ _ [] = panic "minimizeContradiction"
|
||||
$ ("No contradiction" : map (show . ppProp . dpSimpExprProp) ps)
|
||||
go bad prev (d : more) =
|
||||
do assert s d
|
||||
res <- SMT.check (solver s)
|
||||
case res of
|
||||
SMT.Unsat -> do solPop s
|
||||
assert s d
|
||||
start (d : bad) prev
|
||||
_ -> go bad (d : prev) more
|
||||
|
||||
|
||||
|
||||
{- | Attempt to find a substituion that, when applied, makes all of the
|
||||
given properties hold. -}
|
||||
getModel :: Solver -> [Cry.Prop] -> IO (Maybe Cry.Subst)
|
||||
getModel s props = withScope s $
|
||||
do ps <- assumeProps s props
|
||||
res <- SMT.check (solver s)
|
||||
let vars = Set.toList $ Set.unions $ map (cryPropFVS . unSimpProp) ps
|
||||
|
||||
case res of
|
||||
SMT.Sat ->
|
||||
do vs <- getVals (solver s) vars
|
||||
-- This is guaranteed to be a model only for the *linear*
|
||||
-- properties, so now we check if it works for the rest too.
|
||||
|
||||
let su1 = fmap K vs
|
||||
ps1 = [ fromMaybe p (apSubst su1 p) | SimpProp p <- ps ]
|
||||
ok p = case crySimplify p of
|
||||
PTrue -> True
|
||||
_ -> False
|
||||
|
||||
su2 = Cry.listSubst
|
||||
[ (x, numTy v) | (UserName x, v) <- Map.toList vs ]
|
||||
|
||||
return (guard (all ok ps1) >> return su2)
|
||||
|
||||
|
||||
_ -> return Nothing
|
||||
|
||||
|
||||
where
|
||||
numTy Inf = Cry.tInf
|
||||
numTy (Nat k) = Cry.tNum k
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
-- | An SMT solver, and some info about declared variables.
|
||||
data Solver = Solver
|
||||
{ solver :: SMT.Solver
|
||||
-- ^ The actual solver
|
||||
|
||||
, declared :: IORef VarInfo
|
||||
-- ^ Information about declared variables, and assumptions in scope.
|
||||
|
||||
, logger :: SMT.Logger
|
||||
-- ^ For debugging
|
||||
}
|
||||
|
||||
loadFile :: Solver -> FilePath -> IO ()
|
||||
loadFile s file = go . dropComments =<< StrictIO.readFile file
|
||||
where
|
||||
go txt
|
||||
| all isSpace txt = return ()
|
||||
| otherwise = case SMT.readSExpr txt of
|
||||
Just (e,rest) -> SMT.command (solver s) e >> go rest
|
||||
Nothing -> panic "loadFile" [ "Failed to parse SMT file."
|
||||
, txt
|
||||
]
|
||||
|
||||
dropComments = unlines . map dropComment . lines
|
||||
dropComment xs = case break (== ';') xs of
|
||||
(as,_:_) -> as
|
||||
_ -> xs
|
||||
|
||||
rawSolver :: Solver -> SMT.Solver
|
||||
rawSolver = solver
|
||||
|
||||
|
||||
|
||||
-- | Keeps track of declared variables and non-linear terms.
|
||||
data VarInfo = VarInfo
|
||||
{ curScope :: Scope
|
||||
, otherScopes :: [Scope]
|
||||
} deriving Show
|
||||
|
||||
data Scope = Scope
|
||||
{ scopeNames :: [Name]
|
||||
-- ^ Variables declared in this scope (not counting the ones from
|
||||
-- previous scopes).
|
||||
|
||||
, scopeNonLinS :: NonLinS
|
||||
{- ^ These are the non-linear terms mentioned in the assertions
|
||||
that are currently asserted (including ones from previous scopes). -}
|
||||
|
||||
, scopeIntervals :: Either Cry.TVar (Map.Map Cry.TVar Interval)
|
||||
-- ^ Either a type variable that makes the asserted properties unsatisfiable
|
||||
-- (due to a broken interval), or the current set of intervals for type
|
||||
-- variables. If a variable is not in the interval map, its value can be
|
||||
-- anything.
|
||||
--
|
||||
-- This includes all intervals from previous scopes.
|
||||
|
||||
, scopeAsserted :: [Cry.Prop]
|
||||
-- ^ This is the set of currently-asserted cryptol properties only in this
|
||||
-- scope.
|
||||
--
|
||||
-- This includes all asserted props from previous scopes.
|
||||
} deriving Show
|
||||
|
||||
scopeEmpty :: Scope
|
||||
scopeEmpty = Scope { scopeNames = [], scopeNonLinS = initialNonLinS
|
||||
, scopeIntervals = Right Map.empty, scopeAsserted = [] }
|
||||
|
||||
scopeElem :: Name -> Scope -> Bool
|
||||
scopeElem x Scope { .. } = x `elem` scopeNames
|
||||
|
||||
scopeInsert :: Name -> Scope -> Scope
|
||||
scopeInsert x Scope { .. } = Scope { scopeNames = x : scopeNames, .. }
|
||||
|
||||
scopeAssertNew :: Cry.Prop -> Scope -> Scope
|
||||
scopeAssertNew prop Scope { .. } =
|
||||
Scope { scopeIntervals = ints'
|
||||
, scopeAsserted = props
|
||||
, .. }
|
||||
|
||||
where
|
||||
props = prop : scopeAsserted
|
||||
ints' = case scopeIntervals of
|
||||
Left tv -> Left tv
|
||||
Right ints -> case computePropIntervals ints props of
|
||||
NoChange -> scopeIntervals
|
||||
NewIntervals is -> Right is
|
||||
InvalidInterval tv -> Left tv
|
||||
|
||||
|
||||
-- | Given a *simplified* prop, separate linear and non-linear parts
|
||||
-- and return the linear ones.
|
||||
scopeAssertSimpProp :: SimpProp -> Scope -> ([SimpProp],Scope)
|
||||
scopeAssertSimpProp (SimpProp p) Scope { .. } =
|
||||
let (ps1,s1) = nonLinProp scopeNonLinS p
|
||||
in (map SimpProp ps1, Scope { scopeNonLinS = s1, .. })
|
||||
|
||||
|
||||
scopeAssert :: HasProp a => DefinedProp a -> Scope -> ([SimpProp],Scope)
|
||||
scopeAssert DefinedProp { .. } s =
|
||||
let (ps1,s1) = scopeAssertSimpProp dpSimpProp s
|
||||
in (ps1,scopeAssertNew (getProp dpData) s1)
|
||||
|
||||
|
||||
-- | No scopes.
|
||||
viEmpty :: VarInfo
|
||||
viEmpty = VarInfo { curScope = scopeEmpty, otherScopes = [] }
|
||||
|
||||
-- | Check if a name is any of the scopes.
|
||||
viElem :: Name -> VarInfo -> Bool
|
||||
viElem x VarInfo { .. } = any (x `scopeElem`) (curScope : otherScopes)
|
||||
|
||||
-- | Add a name to a scope.
|
||||
viInsert :: Name -> VarInfo -> VarInfo
|
||||
viInsert x VarInfo { .. } = VarInfo { curScope = scopeInsert x curScope, .. }
|
||||
|
||||
-- | Add an assertion to the current scope. Returns the linear part.
|
||||
viAssertSimpProp :: SimpProp -> VarInfo -> (VarInfo, [SimpProp])
|
||||
viAssertSimpProp p VarInfo { .. } = ( VarInfo { curScope = s1, .. }, p1)
|
||||
where (p1, s1) = scopeAssertSimpProp p curScope
|
||||
|
||||
viAssert :: HasProp a => DefinedProp a -> VarInfo -> (VarInfo, [SimpProp])
|
||||
viAssert d VarInfo { .. } = (VarInfo { curScope = s1, .. },p1)
|
||||
where (p1, s1) = scopeAssert d curScope
|
||||
|
||||
-- | Enter a scope.
|
||||
viPush :: VarInfo -> VarInfo
|
||||
viPush VarInfo { .. } =
|
||||
VarInfo { curScope = scopeEmpty { scopeNonLinS = scopeNonLinS curScope
|
||||
, scopeAsserted = scopeAsserted curScope
|
||||
, scopeIntervals = scopeIntervals curScope }
|
||||
, otherScopes = curScope : otherScopes
|
||||
}
|
||||
|
||||
-- | Exit a scope.
|
||||
viPop :: VarInfo -> VarInfo
|
||||
viPop VarInfo { .. } = case otherScopes of
|
||||
c : cs -> VarInfo { curScope = c, otherScopes = cs }
|
||||
_ -> panic "viPop" ["no more scopes"]
|
||||
|
||||
|
||||
-- | All declared names, that have not been "marked".
|
||||
-- These are the variables whose values we are interested in.
|
||||
viUnmarkedNames :: VarInfo -> [ Name ]
|
||||
viUnmarkedNames VarInfo { .. } = concatMap scopeNames scopes
|
||||
where scopes = curScope : otherScopes
|
||||
|
||||
|
||||
getIntervals :: Solver -> IO (Either Cry.TVar (Map Cry.TVar Interval))
|
||||
getIntervals Solver { .. } =
|
||||
do vi <- readIORef declared
|
||||
return (scopeIntervals (curScope vi))
|
||||
|
||||
|
||||
-- | All known non-linear terms.
|
||||
getNLSubst :: Solver -> IO Subst
|
||||
getNLSubst Solver { .. } =
|
||||
do VarInfo { .. } <- readIORef declared
|
||||
return $ nonLinSubst $ scopeNonLinS curScope
|
||||
|
||||
-- | Execute a computation with a fresh solver instance.
|
||||
withSolver :: SolverConfig -> (Solver -> IO a) -> IO a
|
||||
withSolver SolverConfig { .. } k =
|
||||
do logger <- if solverVerbose > 0 then SMT.newLogger 0 else return quietLogger
|
||||
|
||||
|
||||
let smtDbg = if solverVerbose > 1 then Just logger else Nothing
|
||||
solver <- SMT.newSolver solverPath solverArgs smtDbg
|
||||
_ <- SMT.setOptionMaybe solver ":global-decls" "false"
|
||||
-- SMT.setLogic solver "QF_LIA"
|
||||
declared <- newIORef viEmpty
|
||||
a <- k Solver { .. }
|
||||
_ <- SMT.stop solver
|
||||
|
||||
return a
|
||||
|
||||
where
|
||||
quietLogger = SMT.Logger { SMT.logMessage = \_ -> return ()
|
||||
, SMT.logLevel = return 0
|
||||
, SMT.logSetLevel= \_ -> return ()
|
||||
, SMT.logTab = return ()
|
||||
, SMT.logUntab = return ()
|
||||
}
|
||||
|
||||
solPush :: Solver -> IO ()
|
||||
solPush Solver { .. } =
|
||||
do SMT.push solver
|
||||
SMT.logTab logger
|
||||
modifyIORef' declared viPush
|
||||
|
||||
solPop :: Solver -> IO ()
|
||||
solPop Solver { .. } =
|
||||
do modifyIORef' declared viPop
|
||||
SMT.logUntab logger
|
||||
SMT.pop solver
|
||||
|
||||
-- | Execute a computation in a new solver scope.
|
||||
withScope :: Solver -> IO a -> IO a
|
||||
withScope s k =
|
||||
do solPush s
|
||||
a <- k
|
||||
solPop s
|
||||
return a
|
||||
|
||||
-- | Declare a variable.
|
||||
declareVar :: Solver -> Name -> IO ()
|
||||
declareVar s@Solver { .. } a =
|
||||
do done <- fmap (a `viElem`) (readIORef declared)
|
||||
unless done $
|
||||
do e <- SMT.declare solver (smtName a) SMT.tInt
|
||||
let fin_a = smtFinName a
|
||||
fin <- SMT.declare solver fin_a SMT.tBool
|
||||
SMT.assert solver (SMT.geq e (SMT.int 0))
|
||||
|
||||
nlSu <- getNLSubst s
|
||||
modifyIORef' declared (viInsert a)
|
||||
case Map.lookup a nlSu of
|
||||
Nothing -> return ()
|
||||
Just e' ->
|
||||
do let finDef = crySimplify (Fin e')
|
||||
mapM_ (declareVar s) (Set.toList (cryPropFVS finDef))
|
||||
SMT.assert solver $
|
||||
SMT.eq fin (ifPropToSmtLib (desugarProp finDef))
|
||||
|
||||
|
||||
|
||||
-- | Add an assertion to the current context.
|
||||
-- INVARIANT: Assertion is simplified.
|
||||
assert :: HasProp a => Solver -> DefinedProp a -> IO ()
|
||||
assert _ DefinedProp { dpSimpProp = SimpProp PTrue } = return ()
|
||||
assert s@Solver { .. } def@DefinedProp { dpSimpProp = p } =
|
||||
do debugLog s ("Assuming: " ++ show (ppProp (unSimpProp p)))
|
||||
a <- getIntervals s
|
||||
debugLog s ("Intervals before:" ++ show (either pp ppIntervals a))
|
||||
ps1' <- atomicModifyIORef' declared (viAssert def)
|
||||
b <- getIntervals s
|
||||
debugLog s ("Intervals after:" ++ show (either pp ppIntervals b))
|
||||
let ps1 = map unSimpProp ps1'
|
||||
vs = Set.toList $ Set.unions $ map cryPropFVS ps1
|
||||
mapM_ (declareVar s) vs
|
||||
mapM_ (SMT.assert solver . ifPropToSmtLib . desugarProp) ps1
|
||||
|
||||
|
||||
-- | Add an assertion to the current context.
|
||||
-- INVARIANT: Assertion is simplified.
|
||||
assertSimpProp :: Solver -> SimpProp -> IO ()
|
||||
assertSimpProp _ (SimpProp PTrue) = return ()
|
||||
assertSimpProp s@Solver { .. } p@(SimpProp p0) =
|
||||
do debugLog s ("Assuming: " ++ show (ppProp p0))
|
||||
ps1' <- atomicModifyIORef' declared (viAssertSimpProp p)
|
||||
let ps1 = map unSimpProp ps1'
|
||||
vs = Set.toList $ Set.unions $ map cryPropFVS ps1
|
||||
mapM_ (declareVar s) vs
|
||||
mapM_ (SMT.assert solver . ifPropToSmtLib . desugarProp) ps1
|
||||
|
||||
|
||||
-- | Try to prove a property. The result is 'True' when we are sure that
|
||||
-- the property holds, and 'False' otherwise. In other words, getting `False`
|
||||
-- *does not* mean that the proposition does not hold.
|
||||
prove :: Solver -> Prop -> IO Bool
|
||||
prove _ PTrue = return True
|
||||
prove s@Solver { .. } p =
|
||||
debugBlock s ("Proving: " ++ show (ppProp p)) $
|
||||
withScope s $
|
||||
do assertSimpProp s (simpProp (Not p))
|
||||
res <- SMT.check solver
|
||||
case res of
|
||||
SMT.Unsat -> debugLog s "Proved" >> return True
|
||||
SMT.Unknown -> debugLog s "Not proved" >> return False -- We are not sure
|
||||
SMT.Sat -> debugLog s "Not proved" >> return False
|
||||
-- XXX: If the answer is Sat, it is possible that this is a
|
||||
-- a fake example, as we need to evaluate the nonLinear constraints.
|
||||
-- If they are all satisfied, then we have a genuine counter example.
|
||||
-- Otherwise, we could look for another one...
|
||||
|
||||
|
||||
{- | Check if the current set of assumptions is satisfiable, and find
|
||||
some facts that must hold in any models of the current assumptions.
|
||||
|
||||
Returns `Nothing` if the currently asserted constraints are known to
|
||||
be unsatisfiable.
|
||||
|
||||
Returns `Just (su, sub-goals)` is the current set is satisfiable.
|
||||
* The `su` is a substitution that may be applied to the current constraint
|
||||
set without loosing generality.
|
||||
* The `sub-goals` are additional constraints that must hold if the
|
||||
constraint set is to be satisfiable.
|
||||
-}
|
||||
check :: Solver -> IO (Maybe (Subst, [Prop]))
|
||||
check s@Solver { .. } =
|
||||
do e <- getIntervals s
|
||||
case e of
|
||||
|
||||
Left tv ->
|
||||
do debugLog s ("Invalid interval: " ++ show (pp tv))
|
||||
return Nothing
|
||||
|
||||
Right ints ->
|
||||
do debugLog s ("Intervals:" ++ show (ppIntervals ints))
|
||||
res <- SMT.check solver
|
||||
case res of
|
||||
|
||||
SMT.Unsat ->
|
||||
do debugLog s "Not satisfiable"
|
||||
return Nothing
|
||||
|
||||
SMT.Unknown ->
|
||||
do debugLog s "Unknown"
|
||||
return (Just (Map.empty, []))
|
||||
|
||||
SMT.Sat ->
|
||||
do debugLog s "Satisfiable"
|
||||
(impMap,sideConds) <- debugBlock s "Computing improvements"
|
||||
(getImpSubst s)
|
||||
return (Just (impMap, sideConds))
|
||||
|
||||
|
||||
|
||||
{- | Assuming that we are in a satisfiable state, try to compute an
|
||||
improving substitution. We also return additional constraints that must
|
||||
hold for the currently asserted propositions to hold.
|
||||
-}
|
||||
getImpSubst :: Solver -> IO (Subst,[Prop])
|
||||
getImpSubst s@Solver { .. } =
|
||||
do names <- viUnmarkedNames `fmap` readIORef declared
|
||||
m <- getVals solver names
|
||||
(impSu,sideConditions) <- cryImproveModel solver logger m
|
||||
|
||||
nlSu <- getNLSubst s
|
||||
|
||||
let isNonLinName (SysName {}) = True
|
||||
isNonLinName (UserName {}) = False
|
||||
|
||||
(nlFacts, vFacts) = Map.partitionWithKey (\k _ -> isNonLinName k) impSu
|
||||
|
||||
(vV, vNL) = Map.partition noNLVars vFacts
|
||||
|
||||
nlSu1 = fmap (doAppSubst vV) nlSu
|
||||
|
||||
(vNL_su,vNL_eqs) = Map.partitionWithKey goodDef
|
||||
$ fmap (doAppSubst nlSu1) vNL
|
||||
|
||||
nlSu2 = fmap (doAppSubst vNL_su) nlSu1
|
||||
nlLkp x = case Map.lookup x nlSu2 of
|
||||
Just e -> e
|
||||
Nothing -> panic "getImpSubst"
|
||||
[ "Missing NL variable:", show x ]
|
||||
|
||||
allSides =
|
||||
[ Var a :== e | (a,e) <- Map.toList vNL_eqs ] ++
|
||||
[ nlLkp x :== doAppSubst nlSu2 e | (x,e) <- Map.toList nlFacts ] ++
|
||||
[ doAppSubst nlSu2 si | si <- sideConditions ]
|
||||
|
||||
theImpSu = composeSubst vNL_su vV
|
||||
|
||||
debugBlock s "Improvments" $
|
||||
do debugBlock s "substitution" $
|
||||
mapM_ (debugLog s . dump) (Map.toList theImpSu)
|
||||
debugBlock s "side-conditions" $ debugLog s allSides
|
||||
|
||||
|
||||
return (theImpSu, allSides)
|
||||
|
||||
|
||||
where
|
||||
goodDef k e = not (k `Set.member` cryExprFVS e)
|
||||
|
||||
isNLVar (SysName _) = True
|
||||
isNLVar _ = False
|
||||
|
||||
noNLVars e = all (not . isNLVar) (cryExprFVS e)
|
||||
|
||||
dump (x,e) = show (ppProp (Var x :== e))
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
debugBlock :: Solver -> String -> IO a -> IO a
|
||||
debugBlock s@Solver { .. } name m =
|
||||
do debugLog s name
|
||||
SMT.logTab logger
|
||||
a <- m
|
||||
SMT.logUntab logger
|
||||
return a
|
||||
|
||||
class DebugLog t where
|
||||
debugLog :: Solver -> t -> IO ()
|
||||
|
||||
debugLogList :: Solver -> [t] -> IO ()
|
||||
debugLogList s ts = case ts of
|
||||
[] -> debugLog s "(none)"
|
||||
_ -> mapM_ (debugLog s) ts
|
||||
|
||||
instance DebugLog Char where
|
||||
debugLog s x = SMT.logMessage (logger s) (show x)
|
||||
debugLogList s x = SMT.logMessage (logger s) x
|
||||
|
||||
instance DebugLog a => DebugLog [a] where
|
||||
debugLog = debugLogList
|
||||
|
||||
instance DebugLog a => DebugLog (Maybe a) where
|
||||
debugLog s x = case x of
|
||||
Nothing -> debugLog s "(nothing)"
|
||||
Just a -> debugLog s a
|
||||
|
||||
instance DebugLog Doc where
|
||||
debugLog s x = debugLog s (show x)
|
||||
|
||||
instance DebugLog Cry.Type where
|
||||
debugLog s x = debugLog s (pp x)
|
||||
|
||||
instance DebugLog Goal where
|
||||
debugLog s x = debugLog s (goal x)
|
||||
|
||||
instance DebugLog Cry.Subst where
|
||||
debugLog s x = debugLog s (pp x)
|
||||
|
||||
instance DebugLog Prop where
|
||||
debugLog s x = debugLog s (ppProp x)
|
||||
|
||||
|
||||
|
@ -1,425 +0,0 @@
|
||||
-- |
|
||||
-- Module : $Header$
|
||||
-- Copyright : (c) 2014-2016 Galois, Inc.
|
||||
-- License : BSD3
|
||||
-- Maintainer : cryptol@galois.com
|
||||
-- Stability : provisional
|
||||
-- Portability : portable
|
||||
--
|
||||
-- The sytnax of numeric propositions.
|
||||
|
||||
{-# LANGUAGE Safe #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
module Cryptol.TypeCheck.Solver.Numeric.AST
|
||||
( Name(..), ppName
|
||||
|
||||
, Prop(..), cryPropExprs, cryPropFVS
|
||||
, ppProp, ppPropPrec
|
||||
|
||||
, Expr(..), zero, one, two, inf, cryAnds, cryOrs
|
||||
, cryExprExprs, cryRebuildExpr
|
||||
, cryExprFVS
|
||||
, ppExpr, ppExprPrec
|
||||
|
||||
, Nat'(..)
|
||||
|
||||
, IfExpr, IfExpr'(..), ppIf, ppIfExpr
|
||||
|
||||
, Subst, HasVars(..), cryLet, composeSubst, doAppSubst
|
||||
) where
|
||||
|
||||
import Cryptol.TypeCheck.AST(TVar)
|
||||
import Cryptol.TypeCheck.PP(pp)
|
||||
import Cryptol.TypeCheck.Solver.InfNat ( Nat'(..) )
|
||||
import Cryptol.Utils.PP ( Doc, text, (<+>), hang, ($$), char, (<>)
|
||||
, parens, integer, sep )
|
||||
import Cryptol.Utils.Panic ( panic )
|
||||
import Cryptol.Utils.Misc ( anyJust )
|
||||
|
||||
-- import Data.GenericTrie (TrieKey)
|
||||
import GHC.Generics(Generic)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Map ( Map )
|
||||
import qualified Data.Map as Map
|
||||
import Data.Set ( Set )
|
||||
import qualified Data.Set as Set
|
||||
import qualified Control.Applicative as A
|
||||
import Control.Monad ( liftM, ap )
|
||||
|
||||
|
||||
infixr 2 :||
|
||||
infixr 3 :&&
|
||||
infix 4 :==, :>, :>=, :==:, :>:
|
||||
infixl 6 :+, :-
|
||||
infixl 7 :*
|
||||
infixr 8 :^^
|
||||
|
||||
|
||||
|
||||
data Name = UserName TVar | SysName Int
|
||||
deriving (Show,Eq,Ord,Generic)
|
||||
|
||||
|
||||
|
||||
-- | Propopsitions, representing Cryptol's numeric constraints (and a bit more).
|
||||
data Prop =
|
||||
|
||||
-- Preidcates on natural numbers with infinity.
|
||||
-- After simplification, the only one of these should be `fin x`,
|
||||
-- where `x` is a variable.
|
||||
|
||||
Fin Expr | Expr :== Expr | Expr :>= Expr | Expr :> Expr
|
||||
|
||||
|
||||
-- Predicate on strict natural numbers (i.e., no infinities)
|
||||
-- Should be introduced by 'cryNatOp', to eliminte 'inf'.
|
||||
| Expr :==: Expr | Expr :>: Expr
|
||||
|
||||
-- Standard logical strucutre>
|
||||
| Prop :&& Prop | Prop :|| Prop
|
||||
| Not Prop
|
||||
| PFalse | PTrue
|
||||
deriving (Eq,Show,Generic)
|
||||
|
||||
|
||||
-- | Expressions, representing Cryptol's numeric types.
|
||||
data Expr = K Nat'
|
||||
| Var Name
|
||||
| Expr :+ Expr -- total
|
||||
| Expr :- Expr -- partial: x >= y, fin y
|
||||
| Expr :* Expr -- total
|
||||
| Div Expr Expr -- partial: fin x, y >= 1
|
||||
| Mod Expr Expr -- partial: fin x, y >= 1
|
||||
| Expr :^^ Expr -- total
|
||||
| Min Expr Expr -- total
|
||||
| Max Expr Expr -- total
|
||||
| Width Expr -- total
|
||||
| LenFromThen Expr Expr Expr -- partial: x /= y, w >= width x
|
||||
| LenFromThenTo Expr Expr Expr -- partial: x /= y
|
||||
deriving (Eq,Show,Generic,Ord)
|
||||
|
||||
|
||||
-- | The constant @0@.
|
||||
zero :: Expr
|
||||
zero = K (Nat 0)
|
||||
|
||||
-- | The constant @1@.
|
||||
one :: Expr
|
||||
one = K (Nat 1)
|
||||
|
||||
-- | The constant @2@.
|
||||
two :: Expr
|
||||
two = K (Nat 2)
|
||||
|
||||
-- | The constant @infinity@.
|
||||
inf :: Expr
|
||||
inf = K Inf
|
||||
|
||||
|
||||
-- | Make a conjucntion of the given properties.
|
||||
cryAnds :: [Prop] -> Prop
|
||||
cryAnds [] = PTrue
|
||||
cryAnds ps = foldr1 (:&&) ps
|
||||
|
||||
-- | Make a disjunction of the given properties.
|
||||
cryOrs :: [Prop] -> Prop
|
||||
cryOrs [] = PFalse
|
||||
cryOrs ps = foldr1 (:||) ps
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Compute all expressions in a property.
|
||||
cryPropExprs :: Prop -> [Expr]
|
||||
cryPropExprs = go []
|
||||
where
|
||||
go es prop =
|
||||
case prop of
|
||||
PTrue -> es
|
||||
PFalse -> es
|
||||
Not p -> go es p
|
||||
p :&& q -> go (go es q) p
|
||||
p :|| q -> go (go es q) p
|
||||
|
||||
Fin x -> x : es
|
||||
|
||||
x :== y -> x : y : es
|
||||
x :> y -> x : y : es
|
||||
x :>= y -> x : y : es
|
||||
|
||||
x :==: y -> x : y : es
|
||||
x :>: y -> x : y : es
|
||||
|
||||
|
||||
-- | Compute the immediate sub-expressions of an expression.
|
||||
cryExprExprs :: Expr -> [Expr]
|
||||
cryExprExprs expr =
|
||||
case expr of
|
||||
K _ -> []
|
||||
Var _ -> []
|
||||
x :+ y -> [x,y]
|
||||
x :- y -> [x,y]
|
||||
x :* y -> [x,y]
|
||||
Div x y -> [x,y]
|
||||
Mod x y -> [x,y]
|
||||
x :^^ y -> [x,y]
|
||||
Min x y -> [x,y]
|
||||
Max x y -> [x,y]
|
||||
Width x -> [x]
|
||||
LenFromThen x y z -> [x,y,z]
|
||||
LenFromThenTo x y z -> [x,y,z]
|
||||
|
||||
-- | Rebuild an expression, using the top-level strucutre of the first
|
||||
-- expression, but the second list of expressions as sub-expressions.
|
||||
cryRebuildExpr :: Expr -> [Expr] -> Expr
|
||||
cryRebuildExpr expr args =
|
||||
case (expr,args) of
|
||||
(K _, []) -> expr
|
||||
(Var _, []) -> expr
|
||||
(_ :+ _k, [x,y]) -> x :+ y
|
||||
(_ :- _ , [x,y]) -> x :- y
|
||||
(_ :* _ , [x,y]) -> x :* y
|
||||
(Div _ _, [x,y]) -> Div x y
|
||||
(Mod _ _, [x,y]) -> Mod x y
|
||||
(_ :^^ _, [x,y]) -> x :^^ y
|
||||
(Min _ _, [x,y]) -> Min x y
|
||||
(Max _ _, [x,y]) -> Max x y
|
||||
(Width _, [x]) -> Width x
|
||||
(LenFromThen _ _ _ , [x,y,z]) -> LenFromThen x y z
|
||||
(LenFromThenTo _ _ _ , [x,y,z]) -> LenFromThenTo x y z
|
||||
_ -> panic "cryRebuildExpr" $ map show
|
||||
$ text "expr:" <+> ppExpr expr
|
||||
: [ text "arg:" <+> ppExpr a | a <- args ]
|
||||
|
||||
|
||||
-- | Compute the free variables in an expression.
|
||||
cryExprFVS :: Expr -> Set Name
|
||||
cryExprFVS expr =
|
||||
case expr of
|
||||
Var x -> Set.singleton x
|
||||
_ -> Set.unions (map cryExprFVS (cryExprExprs expr))
|
||||
|
||||
-- | Compute the free variables in a proposition.
|
||||
cryPropFVS :: Prop -> Set Name
|
||||
cryPropFVS = Set.unions . map cryExprFVS . cryPropExprs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
data IfExpr' p a = If p (IfExpr' p a) (IfExpr' p a) | Return a | Impossible
|
||||
deriving Eq
|
||||
|
||||
type IfExpr = IfExpr' Prop
|
||||
|
||||
instance Monad (IfExpr' p) where
|
||||
return = Return
|
||||
fail _ = Impossible
|
||||
m >>= k = case m of
|
||||
Impossible -> Impossible
|
||||
Return a -> k a
|
||||
If p t e -> If p (t >>= k) (e >>= k)
|
||||
|
||||
instance Functor (IfExpr' p) where
|
||||
fmap = liftM
|
||||
|
||||
instance A.Applicative (IfExpr' p) where
|
||||
pure = return
|
||||
(<*>) = ap
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Substitution
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
type Subst = Map Name Expr
|
||||
|
||||
composeSubst :: Subst -> Subst -> Subst
|
||||
composeSubst g f = Map.union f' g
|
||||
where
|
||||
f' = fmap (\e -> fromMaybe e (apSubst g e)) f
|
||||
|
||||
cryLet :: HasVars e => Name -> Expr -> e -> Maybe e
|
||||
cryLet x e = apSubst (Map.singleton x e)
|
||||
|
||||
doAppSubst :: HasVars a => Subst -> a -> a
|
||||
doAppSubst su a = fromMaybe a (apSubst su a)
|
||||
|
||||
-- | Replaces occurances of the name with the expression.
|
||||
-- Returns 'Nothing' if there were no occurances of the name.
|
||||
class HasVars ast where
|
||||
apSubst :: Subst -> ast -> Maybe ast
|
||||
|
||||
-- | This is used in the simplification to "apply" substitutions to Props.
|
||||
instance HasVars Bool where
|
||||
apSubst _ _ = Nothing
|
||||
|
||||
instance HasVars Expr where
|
||||
apSubst su = go
|
||||
where
|
||||
go expr =
|
||||
case expr of
|
||||
K _ -> Nothing
|
||||
Var b -> Map.lookup b su
|
||||
x :+ y -> bin (:+) x y
|
||||
x :- y -> bin (:-) x y
|
||||
x :* y -> bin (:*) x y
|
||||
x :^^ y -> bin (:^^) x y
|
||||
Div x y -> bin Div x y
|
||||
Mod x y -> bin Mod x y
|
||||
Min x y -> bin Min x y
|
||||
Max x y -> bin Max x y
|
||||
Width x -> Width `fmap` go x
|
||||
LenFromThen x y w -> three LenFromThen x y w
|
||||
LenFromThenTo x y z -> three LenFromThen x y z
|
||||
|
||||
bin f x y = do [x',y'] <- anyJust go [x,y]
|
||||
return (f x' y')
|
||||
|
||||
three f x y z = do [x',y',z'] <- anyJust go [x,y,z]
|
||||
return (f x' y' z')
|
||||
|
||||
instance HasVars Prop where
|
||||
apSubst su = go
|
||||
where
|
||||
go prop =
|
||||
case prop of
|
||||
PFalse -> Nothing
|
||||
PTrue -> Nothing
|
||||
Not p -> Not `fmap` go p
|
||||
p :&& q -> bin (:&&) p q
|
||||
p :|| q -> bin (:||) p q
|
||||
Fin x -> Fin `fmap` apSubst su x
|
||||
x :== y -> twoE (:==) x y
|
||||
x :>= y -> twoE (:>=) x y
|
||||
x :> y -> twoE (:>) x y
|
||||
x :==: y -> twoE (:==:) x y
|
||||
x :>: y -> twoE (:>) x y
|
||||
|
||||
bin f x y = do [x',y'] <- anyJust go [x,y]
|
||||
return (f x' y')
|
||||
|
||||
twoE f x y = do [x',y'] <- anyJust (apSubst su) [x,y]
|
||||
return (f x' y')
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Tries
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- instance TrieKey Name
|
||||
-- instance TrieKey Prop
|
||||
-- instance TrieKey Expr
|
||||
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Pretty Printing
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- | Pretty print a name.
|
||||
ppName :: Name -> Doc
|
||||
ppName name =
|
||||
case name of
|
||||
UserName x -> pp x
|
||||
SysName x -> char '_' <> text (names !! x)
|
||||
|
||||
-- | An infinite list of names, for pretty prinitng.
|
||||
names :: [String]
|
||||
names = concatMap gen [ 0 :: Integer .. ]
|
||||
where
|
||||
gen x = [ a : suff x | a <- [ 'a' .. 'z' ] ]
|
||||
|
||||
suff 0 = ""
|
||||
suff x = show x
|
||||
|
||||
|
||||
|
||||
-- | Pretty print a top-level property.
|
||||
ppProp :: Prop -> Doc
|
||||
ppProp = ppPropPrec 0
|
||||
|
||||
-- | Pretty print a proposition, in the given precedence context.
|
||||
ppPropPrec :: Int -> Prop -> Doc
|
||||
ppPropPrec prec prop =
|
||||
case prop of
|
||||
Fin x -> fun "fin" ppExprPrec x
|
||||
x :== y -> bin "==" 4 1 1 ppExprPrec x y
|
||||
x :>= y -> bin ">=" 4 1 1 ppExprPrec x y
|
||||
x :> y -> bin ">" 4 1 1 ppExprPrec x y
|
||||
|
||||
x :==: y -> bin "==#" 4 1 1 ppExprPrec x y
|
||||
x :>: y -> bin ">#" 4 1 1 ppExprPrec x y
|
||||
|
||||
p :&& q -> bin "&&" 3 1 0 ppPropPrec p q
|
||||
p :|| q -> bin "||" 2 1 0 ppPropPrec p q
|
||||
Not p -> fun "not" ppPropPrec p
|
||||
PTrue -> text "True"
|
||||
PFalse -> text "False"
|
||||
|
||||
where
|
||||
wrap p d = if prec > p then parens d else d
|
||||
|
||||
fun f how x = wrap 10 (text f <+> how 11 x)
|
||||
|
||||
bin op opP lMod rMod how x y =
|
||||
wrap opP (sep [ how (opP + lMod) x, text op, how (opP + rMod) y ])
|
||||
|
||||
|
||||
|
||||
-- | Pretty print an expression at the top level.
|
||||
ppExpr :: Expr -> Doc
|
||||
ppExpr = ppExprPrec 0
|
||||
|
||||
-- | Pretty print an expression, in the given precedence context.
|
||||
ppExprPrec :: Int -> Expr -> Doc
|
||||
ppExprPrec prec expr =
|
||||
case expr of
|
||||
K Inf -> text "inf"
|
||||
K (Nat n) -> integer n
|
||||
Var a -> ppName a
|
||||
x :+ y -> bin "+" 6 0 1 x y
|
||||
x :- y -> bin "-" 6 0 1 x y
|
||||
x :* y -> bin "*" 7 0 1 x y
|
||||
Div x y -> fun "div" [x,y]
|
||||
Mod x y -> fun "mod" [x,y]
|
||||
x :^^ y -> bin "^^" 8 1 0 x y
|
||||
Min x y -> fun "min" [x,y]
|
||||
Max x y -> fun "max" [x,y]
|
||||
Width x -> fun "width" [x]
|
||||
LenFromThen x y w -> fun "lenFromThen" [x,y,w]
|
||||
LenFromThenTo x y z -> fun "lenFromThenTo" [x,y,z]
|
||||
|
||||
where
|
||||
wrap p d = if prec > p then parens d else d
|
||||
|
||||
fun f xs = wrap 10 (text f <+> sep (map (ppExprPrec 11) xs))
|
||||
|
||||
bin op opP lMod rMod x y =
|
||||
wrap opP
|
||||
(ppExprPrec (opP + lMod) x <+> text op <+> ppExprPrec (opP + rMod) y)
|
||||
|
||||
|
||||
|
||||
-- | Pretty print an experssion with ifs.
|
||||
ppIfExpr :: IfExpr Expr -> Doc
|
||||
ppIfExpr = ppIf ppProp ppExpr
|
||||
|
||||
-- | Pretty print an experssion with ifs.
|
||||
ppIf :: (p -> Doc) -> (a -> Doc) -> IfExpr' p a -> Doc
|
||||
ppIf ppAtom ppVal = go
|
||||
where
|
||||
go expr =
|
||||
case expr of
|
||||
If p t e -> hang (text "if" <+> ppAtom p) 2
|
||||
( (text "then" <+> go t) $$
|
||||
(text "else" <+> go e)
|
||||
)
|
||||
Return e -> ppVal e
|
||||
Impossible -> text "<impossible>"
|
||||
|
||||
|
||||
|
@ -1,55 +0,0 @@
|
||||
-- |
|
||||
-- Module : $Header$
|
||||
-- Copyright : (c) 2014-2016 Galois, Inc.
|
||||
-- License : BSD3
|
||||
-- Maintainer : cryptol@galois.com
|
||||
-- Stability : provisional
|
||||
-- Portability : portable
|
||||
|
||||
{-# LANGUAGE Safe #-}
|
||||
module Cryptol.TypeCheck.Solver.Numeric.Defined where
|
||||
|
||||
import Cryptol.TypeCheck.Solver.Numeric.AST
|
||||
import Cryptol.Utils.Panic ( panic )
|
||||
|
||||
-- | A condition ensure that the given *basic* proposition makes sense.
|
||||
cryDefinedProp :: Prop -> Prop
|
||||
cryDefinedProp prop =
|
||||
case prop of
|
||||
Fin x -> cryDefined x
|
||||
x :== y -> cryDefined x :&& cryDefined y
|
||||
x :>= y -> cryDefined x :&& cryDefined y
|
||||
Not p -> cryDefinedProp p
|
||||
_ -> panic "cryDefinedProp" [ "Not a simple property:"
|
||||
, show (ppProp prop)
|
||||
]
|
||||
|
||||
|
||||
-- | Generate a property ensuring that the expression is well-defined.
|
||||
-- This might be a bit too strict. For example, we reject things like
|
||||
-- @max inf (0 - 1)@, which one might think would simplify to @inf@.
|
||||
cryDefined :: Expr -> Prop
|
||||
cryDefined expr =
|
||||
case expr of
|
||||
K _ -> PTrue
|
||||
Var _ -> PTrue -- Variables are always assumed to be OK.
|
||||
-- The idea is that we are going to check for
|
||||
-- defined-ness before instantiating variables.
|
||||
x :+ y -> cryDefined x :&& cryDefined y
|
||||
x :- y -> cryDefined x :&& cryDefined y :&&
|
||||
Fin y :&& x :>= y
|
||||
x :* y -> cryDefined x :&& cryDefined y
|
||||
Div x y -> cryDefined x :&& cryDefined y :&&
|
||||
Fin x :&& y :>= K (Nat 1)
|
||||
Mod x y -> cryDefined x :&& cryDefined y :&&
|
||||
Fin x :&& y :>= K (Nat 1)
|
||||
x :^^ y -> cryDefined x :&& cryDefined y
|
||||
Min x y -> cryDefined x :&& cryDefined y
|
||||
Max x y -> cryDefined x :&& cryDefined y
|
||||
Width x -> cryDefined x
|
||||
LenFromThen x y w ->
|
||||
cryDefined x :&& cryDefined y :&& cryDefined w :&&
|
||||
Fin x :&& Fin y :&& Fin w :&& Not (x :== y) :&& w :>= Width x
|
||||
LenFromThenTo x y z ->
|
||||
cryDefined x :&& cryDefined y :&& cryDefined z :&&
|
||||
Fin x :&& Fin y :&& Fin z :&& Not (x :== y)
|
@ -1,171 +0,0 @@
|
||||
-- |
|
||||
-- Module : $Header$
|
||||
-- Copyright : (c) 2014-2016 Galois, Inc.
|
||||
-- License : BSD3
|
||||
-- Maintainer : cryptol@galois.com
|
||||
-- Stability : provisional
|
||||
-- Portability : portable
|
||||
|
||||
{-# LANGUAGE Safe #-}
|
||||
module Cryptol.TypeCheck.Solver.Numeric.ImportExport
|
||||
( ExportM
|
||||
, exportProp
|
||||
, exportType
|
||||
, runExportM
|
||||
, exportPropM
|
||||
, exportTypeM
|
||||
, importProp
|
||||
, importType
|
||||
) where
|
||||
|
||||
import Cryptol.TypeCheck.Solver.Numeric.AST
|
||||
import qualified Cryptol.TypeCheck.AST as Cry
|
||||
import qualified Cryptol.TypeCheck.SimpType as SCry
|
||||
import MonadLib
|
||||
|
||||
exportProp :: Cry.Prop -> Maybe Prop
|
||||
exportProp = runExportM . exportPropM
|
||||
|
||||
exportType :: Cry.Prop -> Maybe Expr
|
||||
exportType = runExportM . exportTypeM
|
||||
|
||||
runExportM :: ExportM a -> Maybe a
|
||||
runExportM = either (\_ -> Nothing) Just
|
||||
. runId
|
||||
. runExceptionT
|
||||
|
||||
type ExportM = ExceptionT () Id
|
||||
|
||||
exportPropM :: Cry.Prop -> ExportM Prop
|
||||
exportPropM ty =
|
||||
case ty of
|
||||
Cry.TUser _ _ t -> exportPropM t
|
||||
Cry.TRec {} -> raise ()
|
||||
Cry.TVar {} -> raise ()
|
||||
Cry.TCon (Cry.PC pc) ts ->
|
||||
mapM exportTypeM ts >>= \ets ->
|
||||
case (pc, ets) of
|
||||
(Cry.PFin, [t]) -> return (Fin t)
|
||||
(Cry.PEqual, [t1,t2]) -> return (t1 :== t2)
|
||||
(Cry.PNeq, [t1,t2]) -> return (Not (t1 :== t2))
|
||||
(Cry.PGeq, [t1,t2]) -> return (t1 :>= t2)
|
||||
_ -> raise ()
|
||||
Cry.TCon _ _ -> raise ()
|
||||
|
||||
exportTypeM :: Cry.Type -> ExportM Expr
|
||||
exportTypeM ty =
|
||||
case ty of
|
||||
Cry.TUser _ _ t -> exportTypeM t
|
||||
Cry.TRec {} -> raise ()
|
||||
Cry.TVar x -> return $ Var $ UserName x
|
||||
Cry.TCon tc ts ->
|
||||
case tc of
|
||||
Cry.TError {} -> raise ()
|
||||
|
||||
Cry.TC Cry.TCInf -> return (K Inf)
|
||||
Cry.TC (Cry.TCNum x) -> return (K (Nat x))
|
||||
Cry.TC _ -> raise ()
|
||||
|
||||
Cry.TF f ->
|
||||
mapM exportTypeM ts >>= \ets ->
|
||||
case (f, ets) of
|
||||
(Cry.TCAdd, [t1,t2]) -> return (t1 :+ t2)
|
||||
(Cry.TCSub, [t1,t2]) -> return (t1 :- t2)
|
||||
(Cry.TCMul, [t1,t2]) -> return (t1 :* t2)
|
||||
(Cry.TCDiv, [t1,t2]) -> return (Div t1 t2)
|
||||
(Cry.TCMod, [t1,t2]) -> return (Mod t1 t2)
|
||||
(Cry.TCExp, [t1,t2]) -> return (t1 :^^ t2)
|
||||
(Cry.TCMin, [t1,t2]) -> return (Min t1 t2)
|
||||
(Cry.TCMax, [t1,t2]) -> return (Max t1 t2)
|
||||
(Cry.TCWidth, [t1]) -> return (Width t1)
|
||||
(Cry.TCLenFromThen, [t1,t2,t3]) -> return (LenFromThen t1 t2 t3)
|
||||
(Cry.TCLenFromThenTo, [t1,t2,t3]) -> return (LenFromThenTo t1 t2 t3)
|
||||
|
||||
_ -> raise ()
|
||||
|
||||
Cry.PC _ -> raise ()
|
||||
|
||||
importProp :: Prop -> Maybe [Cry.Prop]
|
||||
importProp prop =
|
||||
case prop of
|
||||
PFalse -> Nothing
|
||||
PTrue -> Just []
|
||||
|
||||
Not p -> importProp =<< pNot p
|
||||
p1 :&& p2 -> do ps1 <- importProp p1
|
||||
ps2 <- importProp p2
|
||||
return (ps1 ++ ps2)
|
||||
_ :|| _ -> Nothing
|
||||
|
||||
Fin expr -> do t <- importType expr
|
||||
return [ Cry.pFin t ]
|
||||
|
||||
e1 :== e2 -> do t1 <- importType e1
|
||||
t2 <- importType e2
|
||||
return [t1 Cry.=#= t2]
|
||||
e1 :>= e2 -> do t1 <- importType e1
|
||||
t2 <- importType e2
|
||||
return [t1 Cry.>== t2]
|
||||
_ :> _ -> Nothing
|
||||
e1 :==: e2 -> do t1 <- importType e1
|
||||
t2 <- importType e2
|
||||
-- XXX: Do we need to add fin?
|
||||
return [t1 Cry.=#= t2]
|
||||
_ :>: _ -> Nothing
|
||||
|
||||
where
|
||||
pNot p =
|
||||
case p of
|
||||
PFalse -> Just PTrue
|
||||
PTrue -> Nothing
|
||||
|
||||
Not a -> Just a
|
||||
_ :&& _ -> Nothing
|
||||
a :|| b -> Just (Not a :&& Not b)
|
||||
|
||||
Fin a -> Just (a :== K Inf)
|
||||
_ :== _ -> Nothing
|
||||
_ :>= _ -> Nothing
|
||||
a :> b -> Just (b :>= a)
|
||||
_ :==: _ -> Nothing
|
||||
a :>: b -> Just (b :>= a)
|
||||
-- XXX: Do we need to add Fin on `a` and 'b'?
|
||||
|
||||
|
||||
importType :: Expr -> Maybe Cry.Type
|
||||
importType = go
|
||||
where
|
||||
go expr =
|
||||
case expr of
|
||||
Var x -> case x of
|
||||
UserName v -> return (Cry.TVar v)
|
||||
_ -> Nothing
|
||||
K n -> case n of
|
||||
Nat x -> Just (Cry.tNum x)
|
||||
Inf -> Just (Cry.tInf)
|
||||
x :+ y -> op2 SCry.tAdd x y
|
||||
x :- y -> op2 SCry.tSub x y
|
||||
x :* y -> op2 SCry.tMul x y
|
||||
Div x y -> op2 SCry.tDiv x y
|
||||
Mod x y -> op2 SCry.tMod x y
|
||||
x :^^ y -> op2 SCry.tExp x y
|
||||
Min x y -> op2 SCry.tMin x y
|
||||
Max x y -> op2 SCry.tMax x y
|
||||
Width x -> op1 SCry.tWidth x
|
||||
LenFromThen x y z -> op3 SCry.tLenFromThen x y z
|
||||
LenFromThenTo x y z -> op3 SCry.tLenFromThenTo x y z
|
||||
|
||||
op1 f x =
|
||||
do t <- go x
|
||||
return (f t)
|
||||
|
||||
op2 f x y =
|
||||
do t1 <- go x
|
||||
t2 <- go y
|
||||
return (f t1 t2)
|
||||
|
||||
op3 f x y z =
|
||||
do t1 <- go x
|
||||
t2 <- go y
|
||||
t3 <- go z
|
||||
return (f t1 t2 t3)
|
@ -1,281 +0,0 @@
|
||||
-- |
|
||||
-- Module : $Header$
|
||||
-- Copyright : (c) 2014-2016 Galois, Inc.
|
||||
-- License : BSD3
|
||||
-- Maintainer : cryptol@galois.com
|
||||
-- Stability : provisional
|
||||
-- Portability : portable
|
||||
--
|
||||
-- Separate Non-Linear Constraints
|
||||
-- When we spot a non-linear expression, we name it and add it to a map.
|
||||
--
|
||||
-- If we see the same expression multiple times, then we give it the same name.
|
||||
--
|
||||
-- The body of the non-linear expression is not processed further,
|
||||
-- so the resulting map should not contain any of the newly minted names.
|
||||
|
||||
{-# LANGUAGE Safe, RecordWildCards #-}
|
||||
|
||||
module Cryptol.TypeCheck.Solver.Numeric.NonLin
|
||||
( nonLinProp
|
||||
, NonLinS, nonLinSubst
|
||||
, initialNonLinS
|
||||
, apSubstNL
|
||||
, lookupNL
|
||||
) where
|
||||
|
||||
import Cryptol.TypeCheck.Solver.Numeric.AST
|
||||
import Cryptol.TypeCheck.Solver.Numeric.Simplify
|
||||
import Cryptol.TypeCheck.Solver.Numeric.SimplifyExpr
|
||||
import Cryptol.Utils.Panic(panic)
|
||||
|
||||
-- import Data.GenericTrie (Trie)
|
||||
-- import qualified Data.GenericTrie as Trie
|
||||
import Data.Maybe ( fromMaybe )
|
||||
import MonadLib
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
|
||||
type Trie = Map
|
||||
|
||||
trie_empty :: Map k a
|
||||
trie_empty = Map.empty
|
||||
|
||||
trie_insert :: Expr -> a -> Map Expr a -> Map Expr a
|
||||
trie_insert = Map.insert
|
||||
|
||||
trie_delete :: Expr -> Map Expr a -> Map Expr a
|
||||
trie_delete = Map.delete
|
||||
|
||||
trie_lookup :: Expr -> Map Expr a -> Maybe a
|
||||
trie_lookup = Map.lookup
|
||||
|
||||
|
||||
-- | Factor-out non-linear terms, by naming them.
|
||||
nonLinProp :: NonLinS -> Prop -> ([Prop], NonLinS)
|
||||
nonLinProp s0 prop = (p : ps, s)
|
||||
where ((p,ps),s) = runNL s0 (nonLinPropM prop)
|
||||
|
||||
{- | Apply a substituin to the non-linear expression database.
|
||||
Returns `Nothing` if nothing was affected.
|
||||
Otherwise returns `Just`, and a substitution for non-linear expressions
|
||||
that became linear.
|
||||
|
||||
The definitions of NL terms do not contain other named NL terms,
|
||||
so it does not matter if the substitution contains bindings like @_a = e@.
|
||||
|
||||
There should be no bindings that mention NL term names in the definitions
|
||||
of the substition (i.e, things like @x = _a@ are NOT ok).
|
||||
-}
|
||||
apSubstNL :: Subst -> NonLinS -> Maybe (Subst, [Prop], NonLinS)
|
||||
apSubstNL su s0 = case runNL s0 (mApSubstNL su) of
|
||||
((Nothing,_),_) -> Nothing
|
||||
((Just su1,ps),r) -> Just (su1,ps,r)
|
||||
|
||||
lookupNL :: Name -> NonLinS -> Maybe Expr
|
||||
lookupNL x NonLinS { .. } = Map.lookup x nonLinExprs
|
||||
|
||||
|
||||
runNL :: NonLinS -> NonLinM a -> ((a, [Prop]), NonLinS)
|
||||
runNL s m = runId
|
||||
$ runStateT s
|
||||
$ do a <- m
|
||||
ps <- finishTodos
|
||||
return (a,ps)
|
||||
|
||||
-- | Get the known non-linear terms.
|
||||
nonLinSubst :: NonLinS -> Subst
|
||||
nonLinSubst = nonLinExprs
|
||||
|
||||
-- | The initial state for the linearization process.
|
||||
initialNonLinS :: NonLinS
|
||||
initialNonLinS = NonLinS
|
||||
{ nextName = 0
|
||||
, nonLinExprs = Map.empty
|
||||
, nlKnown = trie_empty
|
||||
, nlTodo = []
|
||||
}
|
||||
|
||||
|
||||
data SubstOneRes = NoChange
|
||||
-- ^ Substitution does not affect the expression.
|
||||
|
||||
| Updated (Maybe (Name,Expr))
|
||||
-- ^ The expression was updated and, maybe, it became linear.
|
||||
|
||||
|
||||
{- | Apply the substituint to all non-linear bindings.
|
||||
Returns `Nothing` if nothing was affected.
|
||||
Otherwise returns `Just`, and a substituion mapping names that used
|
||||
to be non-linear but became linear.
|
||||
|
||||
Note that we may return `Just empty`, indicating that some non-linear
|
||||
expressions were updated, but they remained non-linear. -}
|
||||
mApSubstNL :: Subst -> NonLinM (Maybe Subst)
|
||||
mApSubstNL su =
|
||||
do s <- get
|
||||
answers <- mapM (mApSubstOneNL su) (Map.toList (nonLinExprs s))
|
||||
return (foldr upd Nothing answers)
|
||||
where
|
||||
upd NoChange ch = ch
|
||||
upd (Updated mb) ch = let lsu = fromMaybe Map.empty ch
|
||||
in Just (case mb of
|
||||
Nothing -> lsu
|
||||
Just (x,e) -> Map.insert x e lsu)
|
||||
|
||||
|
||||
mApSubstOneNL :: Subst -> (Name,Expr) -> NonLinM SubstOneRes
|
||||
mApSubstOneNL su (x,e) =
|
||||
case apSubst su e of
|
||||
Nothing -> return NoChange
|
||||
Just e1 ->
|
||||
case crySimpExprMaybe e1 of
|
||||
|
||||
Nothing ->
|
||||
sets $ \NonLinS { .. } ->
|
||||
( Updated Nothing
|
||||
, NonLinS { nonLinExprs = Map.insert x e1 nonLinExprs
|
||||
, nlKnown = trie_insert e1 x (trie_delete e nlKnown)
|
||||
, .. }
|
||||
)
|
||||
|
||||
Just e2
|
||||
| isNonLinOp e2 ->
|
||||
sets $ \NonLinS { .. } ->
|
||||
(Updated Nothing
|
||||
, NonLinS { nonLinExprs = Map.insert x e2 nonLinExprs
|
||||
, nlKnown = trie_insert e2 x (trie_delete e nlKnown)
|
||||
, .. }
|
||||
)
|
||||
|
||||
| otherwise ->
|
||||
do sets_ $ \NonLinS { .. } ->
|
||||
NonLinS { nonLinExprs = Map.delete x nonLinExprs
|
||||
, nlKnown = trie_delete e nlKnown
|
||||
, ..
|
||||
}
|
||||
es <- mapM nonLinExprM (cryExprExprs e2)
|
||||
let e3 = cryRebuildExpr e2 es
|
||||
return (Updated (Just (x,e3)))
|
||||
|
||||
|
||||
|
||||
-- | Is the top-level operator a non-linear one.
|
||||
isNonLinOp :: Expr -> Bool
|
||||
isNonLinOp expr =
|
||||
case expr of
|
||||
K _ -> False
|
||||
Var _ -> False
|
||||
|
||||
_ :+ _ -> False
|
||||
|
||||
_ :- _ -> False
|
||||
|
||||
x :* y ->
|
||||
case (x,y) of
|
||||
(K _, _) -> False
|
||||
(_, K _) -> False
|
||||
_ -> True
|
||||
|
||||
Div _ y ->
|
||||
case y of
|
||||
K (Nat n) -> n == 0
|
||||
_ -> True
|
||||
|
||||
Mod _ y ->
|
||||
case y of
|
||||
K (Nat n) -> n == 0
|
||||
_ -> True
|
||||
|
||||
_ :^^ _ -> True
|
||||
|
||||
Min _ _ -> False
|
||||
|
||||
Max _ _ -> False
|
||||
|
||||
Width _ -> True
|
||||
|
||||
LenFromThen _ _ _ -> True -- See also comment on `LenFromThenTo`
|
||||
|
||||
LenFromThenTo x y _ ->
|
||||
case (x,y) of
|
||||
(K _, K _) -> False
|
||||
_ -> True -- Actually, as long as the difference bettwen
|
||||
-- `x` and `y` is constant we'd be OK, but not
|
||||
-- sure how to do that...
|
||||
|
||||
nlImplied :: Expr -> Name -> [Prop]
|
||||
nlImplied expr x =
|
||||
map crySimplify $
|
||||
case expr of
|
||||
K (Nat n) :^^ e | n >= 2 -> [ Var x :>= one, Var x :>= e :+ one ]
|
||||
Mod _ e -> [ e :>= Var x :+ one ]
|
||||
|
||||
_ -> []
|
||||
|
||||
|
||||
|
||||
|
||||
nonLinPropM :: Prop -> NonLinM Prop
|
||||
nonLinPropM prop =
|
||||
case prop of
|
||||
PFalse -> return PFalse
|
||||
PTrue -> return PTrue
|
||||
Not p -> Not `fmap` nonLinPropM p
|
||||
p :&& q -> (:&&) `fmap` nonLinPropM p `ap` nonLinPropM q
|
||||
p :|| q -> (:||) `fmap` nonLinPropM p `ap` nonLinPropM q
|
||||
Fin (Var _) -> return prop
|
||||
Fin _ -> unexpected
|
||||
x :==: y -> (:==:) `fmap` nonLinExprM x `ap` nonLinExprM y
|
||||
x :>: y -> (:>:) `fmap` nonLinExprM x `ap` nonLinExprM y
|
||||
|
||||
_ :== _ -> unexpected
|
||||
_ :>= _ -> unexpected
|
||||
_ :> _ -> unexpected
|
||||
|
||||
where
|
||||
unexpected = panic "nonLinPropM" [ show (ppProp prop) ]
|
||||
|
||||
|
||||
|
||||
nonLinExprM :: Expr -> NonLinM Expr
|
||||
nonLinExprM expr
|
||||
| isNonLinOp expr = nameExpr expr
|
||||
| otherwise = cryRebuildExpr expr `fmap` mapM nonLinExprM (cryExprExprs expr)
|
||||
|
||||
|
||||
|
||||
type NonLinM = StateT NonLinS Id
|
||||
|
||||
data NonLinS = NonLinS
|
||||
{ nextName :: !Int
|
||||
, nonLinExprs :: Subst
|
||||
, nlKnown :: Trie Expr Name
|
||||
, nlTodo :: [Prop]
|
||||
} deriving Show
|
||||
|
||||
nameExpr :: Expr -> NonLinM Expr
|
||||
nameExpr e = sets $ \s ->
|
||||
case trie_lookup e (nlKnown s) of
|
||||
Just x -> (Var x, s)
|
||||
Nothing ->
|
||||
let x = nextName s
|
||||
n = SysName x
|
||||
s1 = NonLinS { nextName = 1 + x
|
||||
, nonLinExprs = Map.insert n e (nonLinExprs s)
|
||||
, nlKnown = trie_insert e n (nlKnown s)
|
||||
, nlTodo = nlImplied e n ++ nlTodo s
|
||||
}
|
||||
in (Var n, s1)
|
||||
|
||||
|
||||
finishTodos :: NonLinM [Prop]
|
||||
finishTodos =
|
||||
do s <- get
|
||||
case nlTodo s of
|
||||
[] -> return []
|
||||
p : ps ->
|
||||
do set s { nlTodo = ps }
|
||||
p' <- nonLinPropM p
|
||||
ps' <- finishTodos
|
||||
return (p' : ps')
|
@ -1,517 +0,0 @@
|
||||
-- |
|
||||
-- Module : $Header$
|
||||
-- Copyright : (c) 2014-2016 Galois, Inc.
|
||||
-- License : BSD3
|
||||
-- Maintainer : cryptol@galois.com
|
||||
-- Stability : provisional
|
||||
-- Portability : portable
|
||||
--
|
||||
-- Desugar into SMTLIB Terminology
|
||||
|
||||
{-# LANGUAGE Safe #-}
|
||||
|
||||
module Cryptol.TypeCheck.Solver.Numeric.SMT
|
||||
( desugarProp
|
||||
, smtName
|
||||
, smtFinName
|
||||
, ifPropToSmtLib
|
||||
, cryImproveModel
|
||||
, getVal
|
||||
, getVals
|
||||
) where
|
||||
|
||||
import Cryptol.TypeCheck.AST (TVar(TVFree,TVBound))
|
||||
import Cryptol.TypeCheck.Solver.InfNat
|
||||
import Cryptol.TypeCheck.Solver.Numeric.AST
|
||||
import Cryptol.TypeCheck.Solver.Numeric.Simplify(crySimplify)
|
||||
import Cryptol.Utils.Misc ( anyJust )
|
||||
import Cryptol.Utils.Panic ( panic )
|
||||
|
||||
import Data.List ( partition, unfoldr )
|
||||
import Data.Map ( Map )
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Set as Set
|
||||
import SimpleSMT ( SExpr )
|
||||
import qualified SimpleSMT as SMT
|
||||
import MonadLib
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Desugar to SMT
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- XXX: Expanding the if-then-elses could make things large.
|
||||
-- Perhaps keep them as first class things, in hope that the solver
|
||||
-- can do something more clever with that?
|
||||
|
||||
|
||||
-- | Assumes simplified, linear, finite, defined expressions.
|
||||
desugarExpr :: Expr -> IfExpr Expr
|
||||
desugarExpr expr =
|
||||
do es <- mapM desugarExpr (cryExprExprs expr)
|
||||
case (expr,es) of
|
||||
(Min {}, [x,y]) -> If (x :>: y) (return y) (return x)
|
||||
(Max {}, [x,y]) -> If (x :>: y) (return x) (return y)
|
||||
(LenFromThenTo {}, [ x@(K (Nat a)), K (Nat b), z ])
|
||||
|
||||
-- going down
|
||||
| a > b -> If (z :>: x) (return zero)
|
||||
(return (Div (x :- z) step :+ one))
|
||||
|
||||
-- goind up
|
||||
| b > a -> If (x :>: z) (return zero)
|
||||
(return (Div (z :- x) step :+ one))
|
||||
|
||||
where step = K (Nat (abs (a - b)))
|
||||
|
||||
_ -> return (cryRebuildExpr expr es)
|
||||
|
||||
|
||||
-- | Assumes simplified, linear, defined.
|
||||
desugarProp :: Prop -> IfExpr Prop
|
||||
desugarProp prop =
|
||||
case prop of
|
||||
PFalse -> return PFalse
|
||||
PTrue -> return PTrue
|
||||
Not p -> Not `fmap` desugarProp p
|
||||
p :&& q -> (:&&) `fmap` desugarProp p `ap` desugarProp q
|
||||
p :|| q -> (:||) `fmap` desugarProp p `ap` desugarProp q
|
||||
Fin (Var _) -> return prop
|
||||
x :==: y -> (:==:) `fmap` desugarExpr x `ap` desugarExpr y
|
||||
x :>: y -> (:>:) `fmap` desugarExpr x `ap` desugarExpr y
|
||||
|
||||
Fin _ -> unexpected
|
||||
_ :== _ -> unexpected
|
||||
_ :>= _ -> unexpected
|
||||
_ :> _ -> unexpected
|
||||
|
||||
where
|
||||
unexpected = panic "desugarProp" [ show (ppProp prop) ]
|
||||
|
||||
|
||||
ifPropToSmtLib :: IfExpr Prop -> SExpr
|
||||
ifPropToSmtLib ifProp =
|
||||
case ifProp of
|
||||
Impossible -> SMT.bool False -- Sholdn't really matter
|
||||
Return p -> propToSmtLib p
|
||||
If p q r -> SMT.ite (propToSmtLib p) (ifPropToSmtLib q) (ifPropToSmtLib r)
|
||||
|
||||
propToSmtLib :: Prop -> SExpr
|
||||
propToSmtLib prop =
|
||||
case prop of
|
||||
PFalse -> SMT.bool False
|
||||
PTrue -> SMT.bool True
|
||||
Not p -> case p of
|
||||
Fin _ -> SMT.not (propToSmtLib p)
|
||||
|
||||
-- It is IMPORTANT that the fin constraints are outside
|
||||
-- the not.
|
||||
x :>: y -> addFin $ SMT.geq (exprToSmtLib y)
|
||||
(exprToSmtLib x)
|
||||
_ -> unexpected
|
||||
|
||||
|
||||
p :&& q -> SMT.and (propToSmtLib p) (propToSmtLib q)
|
||||
p :|| q -> SMT.or (propToSmtLib p) (propToSmtLib q)
|
||||
Fin (Var x) -> fin x
|
||||
|
||||
{- For the linear constraints, if the term is finite, then all of
|
||||
its variables must have been finite.
|
||||
|
||||
XXX: Adding the `fin` decls at the leaves leads to some duplication:
|
||||
We could add them just once for each conjunctoin of simple formulas,
|
||||
but I am not sure how much this matters.
|
||||
-}
|
||||
x :==: y -> addFin $ SMT.eq (exprToSmtLib x) (exprToSmtLib y)
|
||||
x :>: y -> addFin $ SMT.gt (exprToSmtLib x) (exprToSmtLib y)
|
||||
|
||||
Fin _ -> unexpected
|
||||
_ :== _ -> unexpected
|
||||
_ :>= _ -> unexpected
|
||||
_ :> _ -> unexpected
|
||||
|
||||
where
|
||||
unexpected = panic "propToSmtLib" [ show (ppProp prop) ]
|
||||
fin x = SMT.const (smtFinName x)
|
||||
|
||||
addFin e = foldr (\x' e' -> SMT.and (fin x') e') e
|
||||
(Set.toList (cryPropFVS prop))
|
||||
|
||||
exprToSmtLib :: Expr -> SExpr
|
||||
exprToSmtLib expr =
|
||||
|
||||
case expr of
|
||||
K (Nat n) -> SMT.int n
|
||||
Var a -> SMT.const (smtName a)
|
||||
x :+ y -> SMT.add (exprToSmtLib x) (exprToSmtLib y)
|
||||
x :- y -> SMT.sub (exprToSmtLib x) (exprToSmtLib y)
|
||||
x :* y -> SMT.mul (exprToSmtLib x) (exprToSmtLib y)
|
||||
Div x y -> SMT.div (exprToSmtLib x) (exprToSmtLib y)
|
||||
Mod x y -> SMT.mod (exprToSmtLib x) (exprToSmtLib y)
|
||||
|
||||
K Inf -> unexpected
|
||||
_ :^^ _ -> unexpected
|
||||
Min {} -> unexpected
|
||||
Max {} -> unexpected
|
||||
Width {} -> unexpected
|
||||
LenFromThen {} -> unexpected
|
||||
LenFromThenTo {} -> unexpected
|
||||
|
||||
where
|
||||
unexpected = panic "exprToSmtLib" [ show (ppExpr expr) ]
|
||||
|
||||
|
||||
-- | The name of a variable in the SMT translation.
|
||||
smtName :: Name -> String
|
||||
smtName a = case a of
|
||||
SysName n -> name "s" n
|
||||
UserName tv -> case tv of
|
||||
TVFree n _ _ _ -> name "u" n
|
||||
TVBound n _ -> name "k" n
|
||||
|
||||
where
|
||||
name p n = case divMod n 26 of
|
||||
(q,r) -> p ++ toEnum (fromEnum 'a' + r) :
|
||||
(if q == 0 then "" else show q)
|
||||
|
||||
|
||||
-- | The name of a boolean variable, representing `fin x`.
|
||||
smtFinName :: Name -> String
|
||||
smtFinName x = "fin_" ++ smtName x
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Models
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
{- | Get the value for the given name.
|
||||
* Assumes that we are in a SAT state (i.e., there is a model)
|
||||
* Assumes that the name is in the model -}
|
||||
getVal :: SMT.Solver -> Name -> IO Nat'
|
||||
getVal s a =
|
||||
do yes <- isInf a
|
||||
if yes
|
||||
then return Inf
|
||||
else do v <- SMT.getConst s (smtName a)
|
||||
case v of
|
||||
SMT.Int x | x >= 0 -> return (Nat x)
|
||||
_ -> panic "cryCheck.getVal" [ "Not a natural number", show v ]
|
||||
|
||||
where
|
||||
isInf v = do yes <- SMT.getConst s (smtFinName v)
|
||||
case yes of
|
||||
SMT.Bool ans -> return (not ans)
|
||||
_ -> panic "cryCheck.isInf"
|
||||
[ "Not a boolean value", show yes ]
|
||||
|
||||
|
||||
-- | Get the values for the given names.
|
||||
getVals :: SMT.Solver -> [Name] -> IO (Map Name Nat')
|
||||
getVals s xs =
|
||||
do es <- mapM (getVal s) xs
|
||||
return (Map.fromList (zip xs es))
|
||||
|
||||
|
||||
-- | Convert a bunch of improving equations into an idempotent substitution.
|
||||
-- Assumes that the equations don't have loops.
|
||||
toSubst :: Map Name Expr -> Subst
|
||||
toSubst m0 = last (m0 : unfoldr step m0)
|
||||
where step m = do m1 <- anyJust (apSubst m) m
|
||||
return (m1,m1)
|
||||
|
||||
|
||||
{- | Given a model, compute an improving substitution, implied by the model.
|
||||
The entries in the substitution look like this:
|
||||
|
||||
* @x = A@ variable @x@ must equal constant @A@
|
||||
|
||||
* @x = y@ variable @x@ must equal variable @y@
|
||||
|
||||
* @x = A * y + B@ (coming soon)
|
||||
variable @x@ is a linear function of @y@,
|
||||
@A@ and @B@ are natural numbers.
|
||||
-}
|
||||
|
||||
|
||||
|
||||
{- | We are mostly interested in improving unification variables.
|
||||
However, it is also useful to improve skolem variables, as this could
|
||||
turn non-linear constraints into linear ones. For example, if we
|
||||
have a constraint @x * y = z@, and we can figure out that @x@ must be 5,
|
||||
then we end up with a linear constraint @5 * y = z@.
|
||||
-}
|
||||
cryImproveModel :: SMT.Solver -> SMT.Logger -> Map Name Nat'
|
||||
-> IO (Map Name Expr, [Prop])
|
||||
cryImproveModel solver logger model =
|
||||
do (imps,subGoals) <- go Map.empty [] initialTodo
|
||||
return (toSubst imps, subGoals)
|
||||
|
||||
where
|
||||
-- Process unification variables first. That way, if we get `x = y`, we'd
|
||||
-- prefer `x` to be a unification variable.
|
||||
initialTodo = uncurry (++) $ partition (isUniVar . fst) $ Map.toList model
|
||||
isUniVar x = case x of
|
||||
UserName (TVFree {}) -> True
|
||||
_ -> False
|
||||
|
||||
|
||||
-- done: the set of known improvements
|
||||
-- extra: the collection of inferred sub-goals
|
||||
go done extra [] = return (done,extra)
|
||||
go done extra ((x,e) : rest) =
|
||||
|
||||
-- x = K?
|
||||
do mbCounter <- cryMustEqualK solver (Map.keys model) x e
|
||||
case mbCounter of
|
||||
Nothing -> go (Map.insert x (K e) done) extra rest
|
||||
Just ce -> goV ce done extra [] x e rest
|
||||
|
||||
|
||||
-- ce: a counter example to `x = e`
|
||||
-- done: known improvements
|
||||
-- extra: known sub-goals
|
||||
-- todo: more work to process once we are done with `x`.
|
||||
goV _ done extra todo _ _ [] = go done extra (reverse todo)
|
||||
goV ce done extra todo x e ((y,e') : more)
|
||||
-- x = y?
|
||||
| e == e' = do yesK <- cryMustEqualV solver x y
|
||||
if yesK then go (Map.insert x (Var y) done)
|
||||
extra
|
||||
(reverse todo ++ more)
|
||||
else tryLR
|
||||
|
||||
| otherwise = tryLR
|
||||
|
||||
where
|
||||
next = goV ce done extra ((y,e'):todo) x e more
|
||||
|
||||
tryLR =
|
||||
do mb <- tryLR_with x e y e'
|
||||
case mb of
|
||||
Just (r,subGoals) -> go (Map.insert x r done)
|
||||
(subGoals ++ extra)
|
||||
(reverse todo ++ more)
|
||||
Nothing ->
|
||||
do mb1 <- tryLR_with y e' x e
|
||||
case mb1 of
|
||||
Nothing -> next
|
||||
Just (r, subGoals) -> go (Map.insert y r done)
|
||||
(subGoals ++ extra)
|
||||
(reverse todo ++ more)
|
||||
|
||||
|
||||
tryLR_with v1 v1Expr v2 v2Expr =
|
||||
case ( isUniVar v1
|
||||
, v1Expr
|
||||
, v2Expr
|
||||
, Map.lookup v1 ce
|
||||
, Map.lookup v2 ce
|
||||
) of
|
||||
(True, x1, y1, Just x2, Just y2) ->
|
||||
cryCheckLinRel solver logger v2 v1 (y1,x1) (y2,x2)
|
||||
_ -> return Nothing
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Try to prove the given expression.
|
||||
checkUnsat :: SMT.Solver -> SExpr -> IO Bool
|
||||
checkUnsat s e =
|
||||
do SMT.push s
|
||||
SMT.assert s e
|
||||
res <- SMT.check s
|
||||
SMT.pop s
|
||||
return (res == SMT.Unsat)
|
||||
|
||||
|
||||
-- | Try to prove the given expression.
|
||||
-- If we fail, we try to give a counter example.
|
||||
-- If the answer is unknown, then we return an empty counter example.
|
||||
getCounterExample :: SMT.Solver -> [Name] -> SExpr -> IO (Maybe (Map Name Nat'))
|
||||
getCounterExample s xs e =
|
||||
do SMT.push s
|
||||
SMT.assert s e
|
||||
res <- SMT.check s
|
||||
ans <- case res of
|
||||
SMT.Unsat -> return Nothing
|
||||
SMT.Unknown -> return (Just Map.empty)
|
||||
SMT.Sat -> Just `fmap` getVals s xs
|
||||
SMT.pop s
|
||||
return ans
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Is this the only possible value for the constant, under the current
|
||||
-- assumptions.
|
||||
-- Assumes that we are in a 'Sat' state.
|
||||
-- Returns 'Nothing' if the variables must always match the given value.
|
||||
-- Otherwise, we return a counter-example (which may be empty, if uniknown)
|
||||
cryMustEqualK :: SMT.Solver -> [Name] ->
|
||||
Name -> Nat' -> IO (Maybe (Map Name Nat'))
|
||||
cryMustEqualK solver xs x val =
|
||||
case val of
|
||||
Inf -> getCounterExample solver xs (SMT.const (smtFinName x))
|
||||
Nat n -> getCounterExample solver xs $
|
||||
SMT.not $
|
||||
SMT.and (SMT.const $ smtFinName x)
|
||||
(SMT.eq (SMT.const (smtName x)) (SMT.int n))
|
||||
|
||||
|
||||
|
||||
-- | Do these two variables need to always be the same, under the current
|
||||
-- assumptions.
|
||||
-- Assumes that we are in a 'Sat' state.
|
||||
cryMustEqualV :: SMT.Solver -> Name -> Name -> IO Bool
|
||||
cryMustEqualV solver x y =
|
||||
checkUnsat solver $
|
||||
SMT.not $
|
||||
SMT.or (SMT.not (fin x) `SMT.and` SMT.not (fin y))
|
||||
(fin x `SMT.and` fin y `SMT.and` SMT.eq (var x) (var y))
|
||||
|
||||
where fin a = SMT.const (smtFinName a)
|
||||
var a = SMT.const (smtName a)
|
||||
|
||||
|
||||
-- | Try to find a linear relation between the two variables, based
|
||||
-- on two observed data points.
|
||||
-- NOTE: The variable being defined is the SECOND name.
|
||||
cryCheckLinRel :: SMT.Solver -> SMT.Logger ->
|
||||
{- x -} Name {- ^ Definition in terms of this variable. -} ->
|
||||
{- y -} Name {- ^ Define this variable. -} ->
|
||||
(Nat',Nat') {- ^ Values in one model (x,y) -} ->
|
||||
(Nat',Nat') {- ^ Values in another model (x,y) -} ->
|
||||
IO (Maybe (Expr,[Prop]))
|
||||
{- ^ Either nothing, or an improving expression, and any
|
||||
additional obligations -}
|
||||
cryCheckLinRel s logger x y p1 p2 =
|
||||
-- First, try to find a linear relation that holds in all finite cases.
|
||||
do SMT.push s
|
||||
SMT.assert s (isFin x)
|
||||
SMT.assert s (isFin y)
|
||||
ans <- case (p1,p2) of
|
||||
((Nat x1, Nat y1), (Nat x2, Nat y2)) ->
|
||||
checkLR x1 y1 x2 y2
|
||||
|
||||
((Inf, Inf), (Nat x2, Nat y2)) ->
|
||||
mbGoOn (getFinPt x2) $ \(x1,y1) -> checkLR x1 y1 x2 y2
|
||||
|
||||
((Nat x1, Nat y1), (Inf, Inf)) ->
|
||||
mbGoOn (getFinPt x1) $ \(x2,y2) -> checkLR x1 y1 x2 y2
|
||||
|
||||
_ -> return Nothing
|
||||
|
||||
SMT.pop s
|
||||
|
||||
|
||||
-- Next, check the infinite cases: if @y = A * x + B@, then
|
||||
-- either both @x@ and @y@ must be infinite or they both must be finite.
|
||||
-- Note that we don't consider relations where A = 0: because they
|
||||
-- would be handled when we checked that @y@ is a constant.
|
||||
case ans of
|
||||
Nothing -> return Nothing
|
||||
Just e ->
|
||||
do SMT.push s
|
||||
SMT.assert s (SMT.not (SMT.eq (isFin x) (isFin y)))
|
||||
c <- SMT.check s
|
||||
SMT.pop s
|
||||
case c of
|
||||
SMT.Unsat -> return (Just e)
|
||||
_ -> return Nothing
|
||||
|
||||
where
|
||||
isFin a = SMT.const (smtFinName a)
|
||||
|
||||
-- XXX: Duplicates `cryDefined`
|
||||
-- The constraints are always of the form: x >= K, or K >= x
|
||||
wellDefined e =
|
||||
case e of
|
||||
(K (Nat a) :* t) :- K (Nat b) ->
|
||||
let c = div (b + a - 1) a
|
||||
in [ t :>= K (Nat c) ]
|
||||
|
||||
K (Nat b) :- (K (Nat a) :* t)
|
||||
-> let c = div b a
|
||||
in [ K (Nat c) :>= t ]
|
||||
|
||||
a :- b -> [ a :>= b ]
|
||||
|
||||
_ -> []
|
||||
|
||||
|
||||
checkLR x1 y1 x2 y2 =
|
||||
do SMT.logMessage logger ("checkLR: " ++ show (x1,y1) ++ " "
|
||||
++ show (x2,y2))
|
||||
mbGoOn (return (linRel (x1,y1) (x2,y2))) (\(a,b) ->
|
||||
do let sumTerm v
|
||||
| b == 0 = v
|
||||
| b < 0 = v :- K (Nat (negate b))
|
||||
| otherwise = v :+ K (Nat b)
|
||||
|
||||
expr
|
||||
| a == 1 = sumTerm (Var x)
|
||||
| a < 0 = K (Nat b) :- K (Nat (negate a)) :* Var x
|
||||
| otherwise = sumTerm (K (Nat a) :* Var x)
|
||||
|
||||
SMT.logMessage logger ("candidate: " ++ show (ppProp (Var y :==: expr)))
|
||||
|
||||
proved <- checkUnsat s
|
||||
$ propToSmtLib $ crySimplify
|
||||
$ Not $ Var y :==: expr
|
||||
|
||||
if not proved
|
||||
then SMT.logMessage logger "failed" >> return Nothing
|
||||
else return (Just (expr,wellDefined expr)))
|
||||
|
||||
-- Try to get an example of another point, which is finite, and at
|
||||
-- different @x@ coordinate.
|
||||
getFinPt otherX =
|
||||
do SMT.push s
|
||||
SMT.assert s (SMT.not (SMT.eq (SMT.const (smtName x)) (SMT.int otherX)))
|
||||
smtAns <- SMT.check s
|
||||
ans <- case smtAns of
|
||||
SMT.Sat ->
|
||||
do vX <- SMT.getConst s (smtName x)
|
||||
vY <- SMT.getConst s (smtName y)
|
||||
case (vX, vY) of
|
||||
(SMT.Int vx, SMT.Int vy)
|
||||
| vx >= 0 && vy >= 0 -> return (Just (vx,vy))
|
||||
_ -> return Nothing
|
||||
_ -> return Nothing
|
||||
SMT.pop s
|
||||
return ans
|
||||
|
||||
mbGoOn m k = do ans <- m
|
||||
case ans of
|
||||
Nothing -> return Nothing
|
||||
Just a -> k a
|
||||
|
||||
{- | Compute a linear relation through two concrete points.
|
||||
Try to find a relation of the form @y = a * x + b@.
|
||||
Depending on the signs of @a@ and @b@, we need additional checks,
|
||||
to ensure tha @a * x + b@ is valid.
|
||||
|
||||
y1 = A * x1 + B
|
||||
y2 = A * x2 + B
|
||||
(y2 - y1) = A * (x2 - x1)
|
||||
|
||||
A = (y2 - y1) / (x2 - x1)
|
||||
B = y1 - A * x1
|
||||
-}
|
||||
linRel :: (Integer,Integer) {- ^ First point -} ->
|
||||
(Integer,Integer) {- ^ Second point -} ->
|
||||
Maybe (Integer,Integer) {- ^ (A,B) -}
|
||||
linRel (x1,y1) (x2,y2) =
|
||||
do guard (x1 /= x2)
|
||||
let (a,r) = divMod (y2 - y1) (x2 - x1)
|
||||
guard (r == 0 && a /= 0) -- Not interested in A = 0
|
||||
let b = y1 - a * x1
|
||||
guard $ not $ a < 0 && b < 0 -- No way this will give a natural number.
|
||||
return (a,b)
|
@ -1,843 +0,0 @@
|
||||
-- |
|
||||
-- Module : $Header$
|
||||
-- Copyright : (c) 2014-2016 Galois, Inc.
|
||||
-- License : BSD3
|
||||
-- Maintainer : cryptol@galois.com
|
||||
-- Stability : provisional
|
||||
-- Portability : portable
|
||||
--
|
||||
-- TODO:
|
||||
-- - Putting in a normal form to spot "prove by assumption"
|
||||
-- - Additional simplification rules, namely various cancelation.
|
||||
-- - Things like: lg2 e(x) = x, where we know thate is increasing.
|
||||
|
||||
{-# LANGUAGE Safe, PatternGuards, BangPatterns #-}
|
||||
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
|
||||
|
||||
module Cryptol.TypeCheck.Solver.Numeric.Simplify
|
||||
(
|
||||
-- * Simplify a property
|
||||
crySimplify, crySimplifyMaybe
|
||||
|
||||
-- * Simplify expressions in a prop
|
||||
, crySimpPropExpr, crySimpPropExprMaybe
|
||||
) where
|
||||
|
||||
import Cryptol.TypeCheck.Solver.Numeric.AST
|
||||
import Cryptol.TypeCheck.Solver.Numeric.SimplifyExpr
|
||||
import Cryptol.TypeCheck.Solver.InfNat(genLog,genRoot,rootExact)
|
||||
import Cryptol.Utils.Misc ( anyJust )
|
||||
|
||||
import Control.Monad ( mplus )
|
||||
import Data.List ( sortBy )
|
||||
import Data.Maybe ( fromMaybe )
|
||||
import qualified Data.Set as Set
|
||||
|
||||
|
||||
-- | Simplify a property, if possible.
|
||||
crySimplify :: Prop -> Prop
|
||||
crySimplify p = fromMaybe p (crySimplifyMaybe p)
|
||||
|
||||
-- | Simplify a property, if possible.
|
||||
crySimplifyMaybe :: Prop -> Maybe Prop
|
||||
crySimplifyMaybe p =
|
||||
let mbSimpExprs = simpSubs p
|
||||
exprsSimped = fromMaybe p mbSimpExprs
|
||||
mbRearrange = tryRearrange exprsSimped
|
||||
rearranged = fromMaybe exprsSimped mbRearrange
|
||||
in crySimplify `fmap` (crySimpStep rearranged `mplus` mbRearrange
|
||||
`mplus` mbSimpExprs)
|
||||
|
||||
where
|
||||
tryRearrange q = case q of
|
||||
_ :&& _ -> cryRearrangeAnd q
|
||||
_ :|| _ -> cryRearrangeOr q
|
||||
_ -> Nothing
|
||||
|
||||
simpSubs q = case q of
|
||||
Not a -> Not `fmap` crySimplifyMaybe a
|
||||
a :&& b -> do [a',b'] <- anyJust crySimplifyMaybe [a,b]
|
||||
return (a' :&& b')
|
||||
a :|| b -> do [a',b'] <- anyJust crySimplifyMaybe [a,b]
|
||||
return (a' :|| b')
|
||||
_ -> crySimpPropExprMaybe q
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- | A single simplification step.
|
||||
crySimpStep :: Prop -> Maybe Prop
|
||||
crySimpStep prop =
|
||||
case prop of
|
||||
|
||||
Fin x -> cryIsFin x -- Fin only on variables.
|
||||
|
||||
x :== y -> Just (cryIsEq x y)
|
||||
x :> y -> Just (cryIsGt x y)
|
||||
|
||||
x :>= y ->
|
||||
case (x,y) of
|
||||
-- XXX: DUPLICTION
|
||||
(K (Nat 0), _) -> Just (y :== zero)
|
||||
(K (Nat a), Width b) -> Just (K (Nat (2 ^ a)) :>= b)
|
||||
|
||||
(_, K (Nat 0)) -> Just PTrue
|
||||
(Width e, K (Nat b)) -> Just (e :>= K (Nat (2^(b-1))))
|
||||
|
||||
|
||||
(K Inf, _) -> Just PTrue
|
||||
(_, K Inf) -> Just (x :== inf)
|
||||
_ -> Just (x :== inf :|| one :+ x :> y)
|
||||
|
||||
x :==: y ->
|
||||
case (x,y) of
|
||||
(K a, K b) -> Just (if a == b then PTrue else PFalse)
|
||||
(K (Nat n), _) | Just p <- cryIsNat True n y -> Just p
|
||||
(_, K (Nat n)) | Just p <- cryIsNat True n x -> Just p
|
||||
|
||||
_ | x == y -> Just PTrue
|
||||
| otherwise -> case (x,y) of
|
||||
(Var _, _) -> Nothing
|
||||
(_, Var _) -> Just (y :==: x)
|
||||
_ -> Nothing
|
||||
|
||||
x :>: y ->
|
||||
case (x,y) of
|
||||
(K (Nat n),_) | Just p <- cryNatGt True n y -> Just p
|
||||
(_, K (Nat n)) | Just p <- cryGtNat True n x -> Just p
|
||||
|
||||
_ | x == y -> Just PFalse
|
||||
| otherwise -> Nothing
|
||||
|
||||
|
||||
-- For :&& and :|| we assume that the props have been rearrnaged
|
||||
p :&& q -> cryAnd p q
|
||||
p :|| q -> cryOr p q
|
||||
|
||||
Not p -> cryNot p
|
||||
PFalse -> Nothing
|
||||
PTrue -> Nothing
|
||||
|
||||
|
||||
-- | Rebalance parens, and arrange conjucts so that we can transfer
|
||||
-- information left-to-right.
|
||||
cryRearrangeAnd :: Prop -> Maybe Prop
|
||||
cryRearrangeAnd prop =
|
||||
case rebalance prop of
|
||||
Just p -> Just p
|
||||
Nothing -> cryAnds `fmap` cryRearrange cmpAnd (split prop)
|
||||
where
|
||||
rebalance (a :&& b) =
|
||||
case a of
|
||||
PFalse -> Just PFalse
|
||||
PTrue -> Just b
|
||||
a1 :&& a2 -> Just (a1 :&& (a2 :&& b))
|
||||
_ -> fmap (a :&&) (rebalance b)
|
||||
rebalance _ = Nothing
|
||||
|
||||
split (a :&& b) = a : split b
|
||||
split a = [a]
|
||||
|
||||
|
||||
-- | Rebalance parens, and arrange disjuncts so that we can transfer
|
||||
-- information left-to-right.
|
||||
cryRearrangeOr :: Prop -> Maybe Prop
|
||||
cryRearrangeOr prop =
|
||||
case rebalance prop of
|
||||
Just p -> Just p
|
||||
Nothing -> cryOrs `fmap` cryRearrange cmpOr (split prop)
|
||||
where
|
||||
rebalance (a :|| b) =
|
||||
case a of
|
||||
PFalse -> Just b
|
||||
PTrue -> Just PTrue
|
||||
a1 :|| a2 -> Just (a1 :|| (a2 :|| b))
|
||||
_ -> fmap (a :||) (rebalance b)
|
||||
rebalance _ = Nothing
|
||||
|
||||
split (a :|| b) = a : split b
|
||||
split a = [a]
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Identify propositions that are suiatable for inlining.
|
||||
cryIsDefn :: Prop -> Maybe (Name, Expr)
|
||||
cryIsDefn (Var x :==: e) = if (x `Set.member` cryExprFVS e)
|
||||
then Nothing
|
||||
else Just (x,e)
|
||||
cryIsDefn _ = Nothing
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type PropOrdering = (Int,Prop) -> (Int,Prop) -> Ordering
|
||||
|
||||
{- | Rearrange proposition for conjuctions and disjunctions.
|
||||
|
||||
information left-to-right, so we put proposition with information content
|
||||
on the left.
|
||||
-}
|
||||
cryRearrange :: PropOrdering -> [Prop] -> Maybe [Prop]
|
||||
cryRearrange cmp ps = if ascending keys then Nothing else Just sortedProps
|
||||
where
|
||||
-- We tag each proposition with a number, so that later we can tell easily
|
||||
-- if the propositions got rearranged.
|
||||
(keys, sortedProps) = unzip (sortBy cmp (zip [ 0 :: Int .. ] ps))
|
||||
|
||||
ascending (x : y : zs) = x < y && ascending (y : zs)
|
||||
ascending _ = True
|
||||
|
||||
|
||||
cmpAnd :: PropOrdering
|
||||
cmpAnd (k1,prop1) (k2,prop2) =
|
||||
case (prop1, prop2) of
|
||||
|
||||
-- First comes PFalse, maybe we don't need to do anything
|
||||
(PFalse, PFalse) -> compare k1 k2
|
||||
(PFalse, _) -> LT
|
||||
(_,PFalse) -> GT
|
||||
|
||||
-- Next comes PTrue
|
||||
(PTrue, PTrue) -> compare k1 k2
|
||||
(PTrue, _) -> LT
|
||||
(_,PTrue) -> GT
|
||||
|
||||
-- Next come `not (fin a)` (i.e, a = inf)
|
||||
(Not (Fin (Var x)), Not (Fin (Var y))) -> cmpVars x y
|
||||
(Not (Fin (Var _)), _) -> LT
|
||||
(_, Not (Fin (Var _))) -> GT
|
||||
|
||||
-- Next come definitions: `x = e` (with `x` not in `fvs e`)
|
||||
-- XXX: Inefficient, because we keep recomputing free variables
|
||||
-- (here, and then when actually applying the substitution)
|
||||
_ | Just (x,_) <- mbL
|
||||
, Just (y,_) <- mbR -> cmpVars x y
|
||||
| Just _ <- mbL -> LT
|
||||
| Just _ <- mbR -> GT
|
||||
where
|
||||
mbL = cryIsDefn prop1
|
||||
mbR = cryIsDefn prop2
|
||||
|
||||
-- Next come `fin a`
|
||||
(Fin (Var x), Fin (Var y)) -> cmpVars x y
|
||||
(Fin (Var _), _) -> LT
|
||||
(_, Fin (Var _)) -> GT
|
||||
|
||||
-- Everything else stays as is
|
||||
_ -> compare k1 k2
|
||||
|
||||
where
|
||||
cmpVars x y
|
||||
| x < y = LT
|
||||
| x > y = GT
|
||||
| otherwise = compare k1 k2
|
||||
|
||||
|
||||
cmpOr :: PropOrdering
|
||||
cmpOr (k1,prop1) (k2,prop2) =
|
||||
case (prop1, prop2) of
|
||||
|
||||
-- First comes PTrue, maybe we don't need to do anything
|
||||
(PTrue, PTrue) -> compare k1 k2
|
||||
(PTrue, _) -> LT
|
||||
(_,PTrue) -> GT
|
||||
|
||||
-- Next comes PFalse
|
||||
(PFalse, PFalse) -> compare k1 k2
|
||||
(PFalse, _) -> LT
|
||||
(_,PFalse) -> GT
|
||||
|
||||
-- Next comes `fin a` (because we propagete `a = inf`)
|
||||
(Fin (Var x), Fin (Var y)) -> cmpVars x y
|
||||
(Fin (Var _), _) -> LT
|
||||
(_, Fin (Var _)) -> GT
|
||||
|
||||
-- Next come `not (fin a)` (i.e, propagete (fin a))
|
||||
(Not (Fin (Var x)), Not (Fin (Var y))) -> cmpVars x y
|
||||
(Not (Fin (Var _)), _) -> LT
|
||||
(_, Not (Fin (Var _))) -> GT
|
||||
|
||||
-- we don't propagete (x /= e) for now.
|
||||
|
||||
-- Everything else stays as is
|
||||
_ -> compare k1 k2
|
||||
|
||||
where
|
||||
cmpVars x y
|
||||
| x < y = LT
|
||||
| x > y = GT
|
||||
| otherwise = compare k1 k2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Simplification of ':&&'.
|
||||
-- Assumes arranged conjucntions.
|
||||
-- See 'cryRearrangeAnd'.
|
||||
cryAnd :: Prop -> Prop -> Maybe Prop
|
||||
cryAnd p q =
|
||||
case p of
|
||||
PTrue -> Just q
|
||||
|
||||
PFalse -> Just PFalse
|
||||
|
||||
Not (Fin (Var x))
|
||||
| Just q' <- cryKnownFin x False q -> Just (p :&& q')
|
||||
|
||||
Fin (Var x)
|
||||
| Just q' <- cryKnownFin x True q -> Just (p :&& q')
|
||||
|
||||
_ | Just (x,e) <- cryIsDefn p
|
||||
, Just q' <- cryLet x e q
|
||||
-> Just (p :&& q')
|
||||
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
-- | Simplification of ':||'.
|
||||
-- Assumes arranged disjunctions.
|
||||
-- See 'cryRearrangeOr'
|
||||
cryOr :: Prop -> Prop -> Maybe Prop
|
||||
cryOr p q =
|
||||
case p of
|
||||
PTrue -> Just PTrue
|
||||
|
||||
PFalse -> Just q
|
||||
|
||||
Fin (Var x)
|
||||
| Just q' <- cryKnownFin x False q -> Just (p :|| q')
|
||||
|
||||
Not (Fin (Var x))
|
||||
| Just q' <- cryKnownFin x True q -> Just (p :|| q')
|
||||
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
|
||||
-- | Propagate the fact that the variable is known to be finite ('True')
|
||||
-- or not-finite ('False').
|
||||
-- Note that this may introduce new expression redexes.
|
||||
cryKnownFin :: Name -> Bool -> Prop -> Maybe Prop
|
||||
cryKnownFin a isFin prop =
|
||||
case prop of
|
||||
Fin (Var a') | a == a' -> Just (if isFin then PTrue else PFalse)
|
||||
|
||||
p :&& q -> do [p',q'] <- anyJust (cryKnownFin a isFin) [p,q]
|
||||
return (p' :&& q')
|
||||
|
||||
p :|| q -> do [p',q'] <- anyJust (cryKnownFin a isFin) [p,q]
|
||||
return (p' :|| q')
|
||||
|
||||
Not p -> Not `fmap` cryKnownFin a isFin p
|
||||
|
||||
x :==: y
|
||||
| not isFin, Just [x',y'] <- anyJust (cryLet a inf) [x,y]
|
||||
-> Just (cryNatOp (:==:) x' y')
|
||||
|
||||
x :>: y
|
||||
| not isFin, Just [x',y'] <- anyJust (cryLet a inf) [x,y]
|
||||
-> Just (cryNatOp (:>:) x' y')
|
||||
|
||||
-- All the other cases should be simplified, eventually.
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Negation.
|
||||
cryNot :: Prop -> Maybe Prop
|
||||
cryNot prop =
|
||||
case prop of
|
||||
Fin _ -> Nothing
|
||||
|
||||
x :== y -> Just (x :> y :|| y :> x)
|
||||
x :>= y -> Just (y :> x)
|
||||
x :> y -> Just (y :>= x)
|
||||
|
||||
x :==: y -> Just (x :>: y :|| y :>: x)
|
||||
|
||||
_ :>: _ -> Nothing
|
||||
|
||||
p :&& q -> Just (Not p :|| Not q)
|
||||
p :|| q -> Just (Not p :&& Not q)
|
||||
Not p -> Just p
|
||||
PFalse -> Just PTrue
|
||||
PTrue -> Just PFalse
|
||||
|
||||
|
||||
|
||||
-- | Simplificaiton for @:==@
|
||||
cryIsEq :: Expr -> Expr -> Prop
|
||||
cryIsEq l r =
|
||||
case (l,r) of
|
||||
(K m, K n) -> if m == n then PTrue else PFalse
|
||||
|
||||
(K Inf, _) -> Not (Fin r)
|
||||
(_, K Inf) -> Not (Fin l)
|
||||
|
||||
(Div x y, z) -> x :>= z :* y :&& (one :+ z) :* y :> x
|
||||
|
||||
(K (Nat n),_) | Just p <- cryIsNat False n r -> p
|
||||
(_,K (Nat n)) | Just p <- cryIsNat False n l -> p
|
||||
|
||||
_ -> Not (Fin l) :&& Not (Fin r)
|
||||
:|| Fin l :&& Fin r :&& cryNatOp (:==:) l r
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Simplificatoin for @:>@
|
||||
cryIsGt :: Expr -> Expr -> Prop
|
||||
cryIsGt (K m) (K n) = if m > n then PTrue else PFalse
|
||||
cryIsGt (K (Nat n)) e | Just p <- cryNatGt False n e = p
|
||||
cryIsGt e (K (Nat n)) | Just p <- cryGtNat False n e = p
|
||||
|
||||
cryIsGt x y = Fin y :&& (x :== inf :||
|
||||
Fin x :&& cryNatOp (:>:) x y)
|
||||
|
||||
|
||||
|
||||
-- | Attempt to simplify a @fin@ constraint.
|
||||
-- Assumes a defined input.
|
||||
cryIsFin :: Expr -> Maybe Prop
|
||||
cryIsFin expr =
|
||||
case expr of
|
||||
K Inf -> Just PFalse
|
||||
K (Nat _) -> Just PTrue
|
||||
Var _ -> Nothing
|
||||
t1 :+ t2 -> Just (Fin t1 :&& Fin t2)
|
||||
t1 :- _ -> Just (Fin t1)
|
||||
|
||||
t1 :* t2 -> Just ( Fin t1 :&& Fin t2
|
||||
:|| t1 :== zero :&& t2 :== inf
|
||||
:|| t2 :== zero :&& t1 :== inf
|
||||
)
|
||||
|
||||
Div t1 _ -> Just (Fin t1)
|
||||
Mod _ _ -> Just PTrue
|
||||
|
||||
t1 :^^ t2 ->
|
||||
Just ( Fin t1 :&& Fin t2
|
||||
:|| t1 :== inf :&& t2 :== zero -- inf ^^ 0
|
||||
:|| t2 :== inf :&& (t1 :== zero :|| t1 :== one)
|
||||
-- 0 ^^ inf, 1 ^^ inf
|
||||
)
|
||||
|
||||
Min t1 t2 -> Just (Fin t1 :|| Fin t2)
|
||||
Max t1 t2 -> Just (Fin t1 :&& Fin t2)
|
||||
Width t1 -> Just (Fin t1)
|
||||
LenFromThen _ _ _ -> Just PTrue
|
||||
LenFromThenTo _ _ _ -> Just PTrue
|
||||
|
||||
|
||||
cryIsNat :: Bool -> Integer -> Expr -> Maybe Prop
|
||||
cryIsNat useFinite n expr =
|
||||
case expr of
|
||||
K Inf -> Just PFalse
|
||||
|
||||
K (Nat m) -> Just (if m == n then PTrue else PFalse)
|
||||
|
||||
Var _ | useFinite -> Nothing
|
||||
| otherwise -> Just (Fin expr :&& expr :==: K (Nat n))
|
||||
|
||||
K (Nat m) :+ e2 -> Just $ if m > n then PFalse
|
||||
else eq e2 $ K $ Nat $ n - m
|
||||
|
||||
x :+ y
|
||||
| n == 0 -> Just (eq x zero :&& eq y zero)
|
||||
| n == 1 -> Just (eq x zero :&& eq y one :||
|
||||
eq x one :&& eq y zero)
|
||||
| otherwise -> Nothing
|
||||
|
||||
e1 :- e2 -> Just $ eq (K (Nat n) :+ e2) e1
|
||||
|
||||
K (Nat m) :* e2 ->
|
||||
Just $ if m == 0
|
||||
then if n == 0 then PTrue else PFalse
|
||||
else case divMod n m of
|
||||
(q,r) -> if r == 0 then eq e2 (K (Nat q))
|
||||
else PFalse
|
||||
e1 :* e2
|
||||
| n == 0 -> Just (eq e1 zero :|| eq e2 zero)
|
||||
| n == 1 -> Just (eq e1 one :&& eq e2 one)
|
||||
| otherwise -> Nothing
|
||||
|
||||
-- (x >= n * y) /\ ((n+1) * y > x)
|
||||
Div x y -> Just (gt (one :+ x) (K (Nat n) :* y) :&&
|
||||
gt (K (Nat (n + 1)) :* y) x)
|
||||
|
||||
Mod _ _ | useFinite -> Nothing
|
||||
| otherwise -> Just (cryNatOp (:==:) expr (K (Nat n)))
|
||||
|
||||
|
||||
K (Nat m) :^^ y -> Just $ case genLog n m of
|
||||
Just (a, exact)
|
||||
| exact -> eq y (K (Nat a))
|
||||
_ -> PFalse
|
||||
x :^^ K (Nat m) -> Just $ case rootExact n m of
|
||||
Just a -> eq x (K (Nat a))
|
||||
Nothing -> PFalse
|
||||
x :^^ y
|
||||
| n == 0 -> Just (eq x zero :&& gt y zero)
|
||||
| n == 1 -> Just (eq x one :|| eq y zero)
|
||||
| otherwise -> Nothing
|
||||
|
||||
Min x y
|
||||
| n == 0 -> Just (eq x zero :|| eq y zero)
|
||||
| otherwise -> Just ( eq x (K (Nat n)) :&& gt y (K (Nat (n - 1)))
|
||||
:|| eq y (K (Nat n)) :&& gt x (K (Nat (n - 1)))
|
||||
)
|
||||
|
||||
Max x y -> Just ( eq x (K (Nat n)) :&& gt (K (Nat (n + 1))) y
|
||||
:|| eq y (K (Nat n)) :&& gt (K (Nat (n + 1))) y
|
||||
)
|
||||
|
||||
Width x
|
||||
| n == 0 -> Just (eq x zero)
|
||||
| otherwise -> Just (gt x (K (Nat (2^(n-1) - 1))) :&&
|
||||
gt (K (Nat (2 ^ n))) x)
|
||||
|
||||
LenFromThen x y w
|
||||
| n == 0 -> Just PFalse
|
||||
|
||||
-- See Note [Sequences of Length 1] in 'Cryptol.TypeCheck.Solver.InfNat'
|
||||
| n == 1 -> Just (gt y x :&& gt (y :+ one) (two :^^ w))
|
||||
| otherwise -> Nothing -- XXX: maybe some more?
|
||||
|
||||
|
||||
-- See `nLenFromThenTo` in 'Cryptol.TypeCheck.Solver.InfNat'
|
||||
LenFromThenTo x y z
|
||||
| n == 0 -> Just ( gt x y :&& gt z x
|
||||
:|| gt y x :&& gt x z
|
||||
)
|
||||
|
||||
-- See Note [Sequences of Length 1] in 'Cryptol.TypeCheck.Solver.InfNat'
|
||||
| n == 1 -> Just (gt z y :&& gt (x :+ one) z :||
|
||||
gt y z :&& gt (z :+ one) x)
|
||||
| otherwise -> Nothing -- XXX: maybe some more?
|
||||
|
||||
|
||||
where
|
||||
eq x y = if useFinite then x :==: y else x :== y
|
||||
gt x y = if useFinite then x :>: y else x :> y
|
||||
|
||||
-- | Constant > expression
|
||||
cryNatGt :: Bool -> Integer -> Expr -> Maybe Prop
|
||||
cryNatGt useFinite n expr
|
||||
| n == 0 = Just PFalse
|
||||
| n == 1 = Just (eq expr zero)
|
||||
| otherwise =
|
||||
case expr of
|
||||
K x -> Just $ if Nat n > x then PTrue else PFalse
|
||||
|
||||
Var _ -> Nothing
|
||||
|
||||
K (Nat m) :+ y -> Just $ if n >= m then gt (k (n - m)) y else PFalse
|
||||
_ :+ _ -> Nothing
|
||||
|
||||
x :- y -> Just $ gt (k n :+ y) x
|
||||
|
||||
K (Nat m) :* y
|
||||
| m == 0 -> Just PTrue -- because we know that n > 1
|
||||
| otherwise -> Just $ case divMod n m of
|
||||
(q,0) -> gt (k q) y
|
||||
(0,_) -> eq y zero
|
||||
(q,_) -> gt (k (q + 1)) y
|
||||
_ :* _ -> Nothing
|
||||
|
||||
Div x y -> Just $ gt (k n :* y) x
|
||||
|
||||
Mod _ (K (Nat m))
|
||||
| m <= n -> Just PTrue
|
||||
|
||||
Mod (K (Nat m)) _
|
||||
| m < n -> Just PTrue
|
||||
Mod _ _ -> Nothing
|
||||
|
||||
|
||||
K (Nat m) :^^ y
|
||||
| m == 0 -> Just PTrue -- because n > 1
|
||||
| m == 1 -> Just PTrue -- ditto
|
||||
| otherwise -> do (a,exact) <- genLog n m
|
||||
return $ if exact
|
||||
then gt (k a) y
|
||||
else gt (k (a + 1)) y
|
||||
x :^^ K (Nat m)
|
||||
| m == 0 -> Just PTrue
|
||||
| m == 1 -> Just (gt (k n) x)
|
||||
| otherwise -> do (a,exact) <- genRoot n m
|
||||
return $ if exact
|
||||
then gt (k a) x
|
||||
else gt (k (a + 1)) x
|
||||
_ :^^ _ -> Nothing
|
||||
|
||||
Min x y -> Just $ gt (k n) x :|| gt (k n) y
|
||||
Max x y -> Just $ gt (k n) x :&& gt (k n) y
|
||||
|
||||
Width x -> Just $ gt (k (2 ^ n)) x
|
||||
|
||||
LenFromThen _ _ _ -> Nothing -- Are there some rules?
|
||||
|
||||
LenFromThenTo _ _ _ -> Nothing -- Are there some rulesj
|
||||
|
||||
where
|
||||
k x = K (Nat x)
|
||||
eq x y = if useFinite then x :==: y else x :== y
|
||||
gt x y = if useFinite then x :>: y else x :> y
|
||||
|
||||
|
||||
|
||||
-- | Expression > constant
|
||||
cryGtNat :: Bool -> Integer -> Expr -> Maybe Prop
|
||||
cryGtNat useFinite n expr =
|
||||
case expr of
|
||||
K x -> Just $ if x > Nat n then PTrue else PFalse
|
||||
Var _ -> Nothing
|
||||
|
||||
K (Nat m) :+ y
|
||||
| m > n -> Just PTrue
|
||||
| otherwise -> Just (gt y (K (Nat (n - m))))
|
||||
|
||||
x :+ y
|
||||
| n == 0 -> Just (gt x zero :|| gt y zero)
|
||||
| otherwise -> Nothing
|
||||
|
||||
x :- y -> Just $ gt x (K (Nat n) :+ y)
|
||||
|
||||
|
||||
K (Nat m) :* y
|
||||
| m > 0 -> Just $ case divMod n m of
|
||||
(a,_) -> gt y $ K $ Nat a
|
||||
|
||||
x :* y
|
||||
| n == 0 -> Just (gt x zero :&& gt y zero)
|
||||
| otherwise -> Nothing
|
||||
|
||||
Div x y -> Just $ gt (one :+ x) (K (Nat (n+1)) :* y)
|
||||
|
||||
Mod _ (K (Nat m))
|
||||
| m <= n -> Just PFalse
|
||||
Mod (K (Nat m)) _
|
||||
| m < n -> Just PFalse
|
||||
Mod _ _ -> Nothing
|
||||
|
||||
K (Nat m) :^^ y
|
||||
| m == 0 -> Just $ if n == 0 then eq y zero else PFalse
|
||||
| m == 1 -> Just $ if n == 0 then PTrue else PFalse
|
||||
| otherwise -> do (a,_exact) <- genLog n m
|
||||
Just (gt y (K (Nat a)))
|
||||
|
||||
x :^^ K (Nat m)
|
||||
| m == 0 -> Just $ if n == 0 then PTrue else PFalse
|
||||
| m == 1 -> Just $ gt x (K (Nat n))
|
||||
| otherwise -> do (a,exact) <- genRoot n m
|
||||
Just $ if exact
|
||||
then gt x (K (Nat a))
|
||||
else gt (one :+ x) (K (Nat (a+1)))
|
||||
|
||||
x :^^ y
|
||||
| n == 0 -> Just (gt x zero :|| eq y zero)
|
||||
| otherwise -> Nothing
|
||||
|
||||
Min x y -> Just $ gt x (K (Nat n)) :&& gt y (K (Nat n))
|
||||
Max x y -> Just $ gt x (K (Nat n)) :|| gt y (K (Nat n))
|
||||
|
||||
Width x -> Just $ gt (one :+ x) (K (Nat (2 ^ n)))
|
||||
|
||||
LenFromThen _ _ _
|
||||
| n == 0 -> Just PTrue
|
||||
| otherwise -> Nothing -- Are there some rules?
|
||||
|
||||
LenFromThenTo x y z
|
||||
| n == 0 -> Just (gt x y :&& gt z x :|| gt y x :&& gt x z)
|
||||
| otherwise -> Nothing
|
||||
|
||||
where
|
||||
eq x y = if useFinite then x :==: y else x :== y
|
||||
gt x y = if useFinite then x :>: y else x :> y
|
||||
|
||||
|
||||
|
||||
-- | Simplify only the Expr parts of a Prop.
|
||||
crySimpPropExpr :: Prop -> Prop
|
||||
crySimpPropExpr p = fromMaybe p (crySimpPropExprMaybe p)
|
||||
|
||||
-- | Simplify only the Expr parts of a Prop.
|
||||
-- Returns `Nothing` if there were no changes.
|
||||
crySimpPropExprMaybe :: Prop -> Maybe Prop
|
||||
crySimpPropExprMaybe prop =
|
||||
case prop of
|
||||
|
||||
Fin e -> Fin `fmap` crySimpExprMaybe e
|
||||
|
||||
a :== b -> binop crySimpExprMaybe (:== ) a b
|
||||
a :>= b -> binop crySimpExprMaybe (:>= ) a b
|
||||
a :> b -> binop crySimpExprMaybe (:> ) a b
|
||||
a :==: b -> binop crySimpExprMaybe (:==:) a b
|
||||
a :>: b -> binop crySimpExprMaybe (:>: ) a b
|
||||
|
||||
a :&& b -> binop crySimpPropExprMaybe (:&&) a b
|
||||
a :|| b -> binop crySimpPropExprMaybe (:||) a b
|
||||
|
||||
Not p -> Not `fmap` crySimpPropExprMaybe p
|
||||
|
||||
PFalse -> Nothing
|
||||
PTrue -> Nothing
|
||||
|
||||
where
|
||||
|
||||
binop simp f l r =
|
||||
case (simp l, simp r) of
|
||||
(Nothing,Nothing) -> Nothing
|
||||
(l',r') -> Just (f (fromMaybe l l') (fromMaybe r r'))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Our goal is to bubble @inf@ terms to the top of @Return@.
|
||||
cryNoInf :: Expr -> IfExpr Expr
|
||||
cryNoInf expr =
|
||||
case expr of
|
||||
|
||||
-- These are the interesting cases where we have to branch
|
||||
|
||||
x :* y ->
|
||||
do x' <- cryNoInf x
|
||||
y' <- cryNoInf y
|
||||
case (x', y') of
|
||||
(K Inf, K Inf) -> return inf
|
||||
(K Inf, _) -> mkIf (y' :==: zero) (return zero) (return inf)
|
||||
(_, K Inf) -> mkIf (x' :==: zero) (return zero) (return inf)
|
||||
_ -> return (x' :* y')
|
||||
|
||||
x :^^ y ->
|
||||
do x' <- cryNoInf x
|
||||
y' <- cryNoInf y
|
||||
case (x', y') of
|
||||
(K Inf, K Inf) -> return inf
|
||||
(K Inf, _) -> mkIf (y' :==: zero) (return one) (return inf)
|
||||
(_, K Inf) -> mkIf (x' :==: zero) (return zero)
|
||||
$ mkIf (x' :==: one) (return one)
|
||||
$ return inf
|
||||
_ -> return (x' :^^ y')
|
||||
|
||||
|
||||
|
||||
-- The rest just propagates
|
||||
|
||||
K _ -> return expr
|
||||
Var _ -> return expr
|
||||
|
||||
x :+ y ->
|
||||
do x' <- cryNoInf x
|
||||
y' <- cryNoInf y
|
||||
case (x', y') of
|
||||
(K Inf, _) -> return inf
|
||||
(_, K Inf) -> return inf
|
||||
_ -> return (x' :+ y')
|
||||
|
||||
x :- y ->
|
||||
do x' <- cryNoInf x
|
||||
y' <- cryNoInf y
|
||||
case (x', y') of
|
||||
(_, K Inf) -> Impossible
|
||||
(K Inf, _) -> return inf
|
||||
_ -> mkIf (x' :==: y)
|
||||
(return zero)
|
||||
(mkIf (x' :>: y) (return (x' :- y'))
|
||||
Impossible)
|
||||
|
||||
Div x y ->
|
||||
do x' <- cryNoInf x
|
||||
y' <- cryNoInf y
|
||||
case (x', y') of
|
||||
(K Inf, _) -> Impossible
|
||||
(_, K Inf) -> return zero
|
||||
_ -> mkIf (y' :>: zero) (return (Div x' y')) Impossible
|
||||
|
||||
Mod x y ->
|
||||
do x' <- cryNoInf x
|
||||
-- `Mod x y` is finite, even if `y` is `inf`, so first check
|
||||
-- for finiteness.
|
||||
mkIf (Fin y)
|
||||
(do y' <- cryNoInf y
|
||||
case (x',y') of
|
||||
(K Inf, _) -> Impossible
|
||||
(_, K Inf) -> Impossible
|
||||
_ -> mkIf (y' :>: zero) (return (Mod x' y')) Impossible
|
||||
)
|
||||
(return x')
|
||||
|
||||
Min x y ->
|
||||
do x' <- cryNoInf x
|
||||
y' <- cryNoInf y
|
||||
case (x',y') of
|
||||
(K Inf, _) -> return y'
|
||||
(_, K Inf) -> return x'
|
||||
_ -> return (Min x' y')
|
||||
|
||||
Max x y ->
|
||||
do x' <- cryNoInf x
|
||||
y' <- cryNoInf y
|
||||
case (x', y') of
|
||||
(K Inf, _) -> return inf
|
||||
(_, K Inf) -> return inf
|
||||
_ -> return (Max x' y')
|
||||
|
||||
Width x ->
|
||||
do x' <- cryNoInf x
|
||||
case x' of
|
||||
K Inf -> return inf
|
||||
_ -> return (Width x')
|
||||
|
||||
LenFromThen x y w -> fun3 LenFromThen x y w
|
||||
LenFromThenTo x y z -> fun3 LenFromThenTo x y z
|
||||
|
||||
|
||||
where
|
||||
fun3 f x y z =
|
||||
do x' <- cryNoInf x
|
||||
y' <- cryNoInf y
|
||||
z' <- cryNoInf z
|
||||
case (x',y',z') of
|
||||
(K Inf, _, _) -> Impossible
|
||||
(_, K Inf, _) -> Impossible
|
||||
(_, _, K Inf) -> Impossible
|
||||
_ -> mkIf (x' :==: y') Impossible
|
||||
(return (f x' y' z'))
|
||||
|
||||
mkIf p t e = case crySimplify p of
|
||||
PTrue -> t
|
||||
PFalse -> e
|
||||
p' -> If p' t e
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Make an expression that should work ONLY on natural nubers.
|
||||
-- Eliminates occurances of @inf@.
|
||||
-- Assumes that the two input expressions are well-formed and finite.
|
||||
-- The expression is constructed by the given function.
|
||||
cryNatOp :: (Expr -> Expr -> Prop) -> Expr -> Expr -> Prop
|
||||
cryNatOp op x y =
|
||||
toProp $
|
||||
do x' <- noInf x
|
||||
y' <- noInf y
|
||||
return (op x' y')
|
||||
where
|
||||
noInf a = do a' <- cryNoInf a
|
||||
case a' of
|
||||
K Inf -> Impossible
|
||||
_ -> return a'
|
||||
|
||||
toProp ite =
|
||||
case ite of
|
||||
Impossible -> PFalse -- It doesn't matter, but @false@ might anihilate.
|
||||
Return p -> p
|
||||
If p t e -> p :&& toProp t :|| Not p :&& toProp e
|
@ -1,213 +0,0 @@
|
||||
-- |
|
||||
-- Module : $Header$
|
||||
-- Copyright : (c) 2015-2016 Galois, Inc.
|
||||
-- License : BSD3
|
||||
-- Maintainer : cryptol@galois.com
|
||||
-- Stability : provisional
|
||||
-- Portability : portable
|
||||
--
|
||||
-- Simplification of expressions.
|
||||
-- The result of simplifying an expression `e`, is a new expression `e'`,
|
||||
-- which satisfies the property:
|
||||
--
|
||||
-- if e is well-defined then e and e' will evaluate to the same type.
|
||||
--
|
||||
|
||||
{-# LANGUAGE Safe, BangPatterns #-}
|
||||
module Cryptol.TypeCheck.Solver.Numeric.SimplifyExpr where
|
||||
|
||||
import Cryptol.TypeCheck.Solver.Numeric.AST
|
||||
import qualified Cryptol.TypeCheck.Solver.InfNat as IN
|
||||
import Cryptol.Utils.Misc ( anyJust )
|
||||
|
||||
import Control.Monad ( guard )
|
||||
import Data.Maybe ( fromMaybe, maybeToList )
|
||||
import qualified Data.Map as Map
|
||||
|
||||
|
||||
-- | Simplify an expression, if possible.
|
||||
crySimpExpr :: Expr -> Expr
|
||||
crySimpExpr expr = fromMaybe expr (crySimpExprMaybe expr)
|
||||
|
||||
-- | Perform simplification from the leaves up.
|
||||
-- Returns `Nothing` if there were no changes.
|
||||
crySimpExprMaybe :: Expr -> Maybe Expr
|
||||
crySimpExprMaybe expr =
|
||||
case crySimpExprStep (fromMaybe expr mbE1) of
|
||||
Nothing -> mbE1
|
||||
Just e2 -> Just (fromMaybe e2 (crySimpExprMaybe e2))
|
||||
where
|
||||
mbE1 = cryRebuildExpr expr `fmap` anyJust crySimpExprMaybe (cryExprExprs expr)
|
||||
|
||||
|
||||
|
||||
-- XXX: Add rules to group together occurances of variables
|
||||
|
||||
|
||||
data Sign = Pos | Neg deriving Show
|
||||
|
||||
otherSign :: Sign -> Sign
|
||||
otherSign s = case s of
|
||||
Pos -> Neg
|
||||
Neg -> Pos
|
||||
|
||||
signed :: Sign -> Integer -> Integer
|
||||
signed s = case s of
|
||||
Pos -> id
|
||||
Neg -> negate
|
||||
|
||||
|
||||
splitSum :: Expr -> [(Sign,Expr)]
|
||||
splitSum e0 = go Pos e0 []
|
||||
where go s (e1 :+ e2) es = go s e1 (go s e2 es)
|
||||
go s (e1 :- e2) es = go s e1 (go (otherSign s) e2 es)
|
||||
go s e es = (s,e) : es
|
||||
|
||||
normSum :: Expr -> Expr
|
||||
normSum = posTerm . go 0 Map.empty Nothing . splitSum
|
||||
where
|
||||
|
||||
-- constants, variables, other terms
|
||||
go !_ !_ !_ ((Pos,K Inf) : _) = (Pos, K Inf)
|
||||
|
||||
go k xs t ((s, K (Nat n)) : es) = go (k + signed s n) xs t es
|
||||
|
||||
go k xs t ((s, Var x) : es) = go k (Map.insertWith (+) x (signed s 1) xs) t es
|
||||
|
||||
go k xs t ((s, K (Nat n) :* Var x) : es)
|
||||
| n == 0 = go k xs t es
|
||||
| otherwise = go k (Map.insertWith (+) x (signed s n) xs) t es
|
||||
|
||||
go k xs Nothing (e : es) = go k xs (Just e) es
|
||||
|
||||
go k xs (Just e1) (e2 : es) = go k xs (Just (add e1 e2)) es
|
||||
|
||||
go k xs t [] =
|
||||
let terms = constTerm k
|
||||
++ concatMap varTerm (Map.toList xs)
|
||||
++ maybeToList t
|
||||
|
||||
in case terms of
|
||||
[] -> (Pos, K (Nat 0))
|
||||
ts -> foldr1 add ts
|
||||
|
||||
constTerm k
|
||||
| k == 0 = []
|
||||
| k > 0 = [ (Pos, K (Nat k)) ]
|
||||
| otherwise = [ (Neg, K (Nat (negate k))) ]
|
||||
|
||||
varTerm (x,k)
|
||||
| k == 0 = []
|
||||
| k == 1 = [ (Pos, Var x) ]
|
||||
| k > 0 = [ (Pos, K (Nat k) :* Var x) ]
|
||||
| k == (-1) = [ (Neg, Var x) ]
|
||||
| otherwise = [ (Neg, K (Nat (negate k)) :* Var x) ]
|
||||
|
||||
add (s1,t1) (s2,t2) =
|
||||
case (s1,s2) of
|
||||
(Pos,Pos) -> (Pos, t1 :+ t2)
|
||||
(Pos,Neg) -> (Pos, t1 :- t2)
|
||||
(Neg,Pos) -> (Pos, t2 :- t1)
|
||||
(Neg,Neg) -> (Neg, t1 :+ t2)
|
||||
|
||||
posTerm (Pos,x) = x
|
||||
posTerm (Neg,x) = K (Nat 0) :- x
|
||||
|
||||
|
||||
|
||||
|
||||
crySimpExprStep :: Expr -> Maybe Expr
|
||||
crySimpExprStep e =
|
||||
case crySimpExprStep1 e of
|
||||
Just e1 -> Just e1
|
||||
Nothing -> do let e1 = normSum e
|
||||
guard (e /= e1)
|
||||
return e1
|
||||
|
||||
-- | Make a simplification step, assuming the expression is well-formed.
|
||||
crySimpExprStep1 :: Expr -> Maybe Expr
|
||||
crySimpExprStep1 expr =
|
||||
case expr of
|
||||
K _ -> Nothing
|
||||
Var _ -> Nothing
|
||||
|
||||
_ :+ _ -> Nothing
|
||||
_ :- _ -> Nothing
|
||||
|
||||
x :* y ->
|
||||
case (x,y) of
|
||||
(K (Nat 0), _) -> Just zero
|
||||
(K (Nat 1), _) -> Just y
|
||||
(K a, K b) -> Just (K (IN.nMul a b))
|
||||
(_, K _) -> Just (y :* x)
|
||||
|
||||
(K a, K b :* z) -> Just (K (IN.nMul a b) :* z)
|
||||
|
||||
-- Normalize, somewhat
|
||||
(a :* b, _) -> Just (a :* (b :* y))
|
||||
(Var a, Var b)
|
||||
| b > a -> Just (y :* x)
|
||||
|
||||
_ -> Nothing
|
||||
|
||||
Div x y ->
|
||||
case (x,y) of
|
||||
(K (Nat 0), _) -> Just zero
|
||||
(_, K (Nat 1)) -> Just x
|
||||
(_, K Inf) -> Just zero
|
||||
(K a, K b) -> K `fmap` IN.nDiv a b
|
||||
_ | x == y -> Just one
|
||||
_ -> Nothing
|
||||
|
||||
Mod x y ->
|
||||
case (x,y) of
|
||||
(K (Nat 0), _) -> Just zero
|
||||
(_, K Inf) -> Just x
|
||||
(_, K (Nat 1)) -> Just zero
|
||||
(K a, K b) -> K `fmap` IN.nMod a b
|
||||
_ -> Nothing
|
||||
|
||||
x :^^ y ->
|
||||
case (x,y) of
|
||||
(_, K (Nat 0)) -> Just one
|
||||
(_, K (Nat 1)) -> Just x
|
||||
(K (Nat 1), _) -> Just one
|
||||
(K a, K b) -> Just (K (IN.nExp a b))
|
||||
_ -> Nothing
|
||||
|
||||
Min x y ->
|
||||
case (x,y) of
|
||||
(K (Nat 0), _) -> Just zero
|
||||
(K Inf, _) -> Just y
|
||||
(_, K (Nat 0)) -> Just zero
|
||||
(_, K Inf) -> Just x
|
||||
(K a, K b) -> Just (K (IN.nMin a b))
|
||||
_ | x == y -> Just x
|
||||
_ -> Nothing
|
||||
|
||||
Max x y ->
|
||||
case (x,y) of
|
||||
(K (Nat 0), _) -> Just y
|
||||
(K Inf, _) -> Just inf
|
||||
(_, K (Nat 0)) -> Just x
|
||||
(_, K Inf) -> Just inf
|
||||
(K a, K b) -> Just (K (IN.nMax a b))
|
||||
_ | x == y -> Just x
|
||||
_ -> Nothing
|
||||
|
||||
Width x ->
|
||||
case x of
|
||||
K a -> Just (K (IN.nWidth a))
|
||||
K (Nat 2) :^^ e -> Just (one :+ e)
|
||||
K (Nat 2) :^^ e :- K (Nat 1) -> Just e
|
||||
_ -> Nothing
|
||||
|
||||
LenFromThen x y w ->
|
||||
case (x,y,w) of
|
||||
(K a, K b, K c) -> K `fmap` IN.nLenFromThen a b c
|
||||
_ -> Nothing
|
||||
|
||||
LenFromThenTo x y z ->
|
||||
case (x,y,z) of
|
||||
(K a, K b, K c) -> K `fmap` IN.nLenFromThenTo a b c
|
||||
_ -> Nothing
|
@ -1,5 +1,31 @@
|
||||
-- |
|
||||
-- Module : $Header$
|
||||
-- Copyright : (c) 2013-2016 Galois, Inc.
|
||||
-- License : BSD3
|
||||
-- Maintainer : cryptol@galois.com
|
||||
-- Stability : provisional
|
||||
-- Portability : portable
|
||||
{-# LANGUAGE Safe #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# Language FlexibleInstances #-}
|
||||
module Cryptol.TypeCheck.Solver.SMT (proveImp,checkUnsolvable) where
|
||||
{-# LANGUAGE PatternGuards #-}
|
||||
{-# LANGUAGE TypeSynonymInstances #-}
|
||||
|
||||
module Cryptol.TypeCheck.Solver.SMT
|
||||
( -- * Setup
|
||||
Solver
|
||||
, withSolver
|
||||
|
||||
-- * Debugging
|
||||
, debugBlock
|
||||
, debugLog
|
||||
|
||||
-- * Proving stuff
|
||||
, proveImp
|
||||
, checkUnsolvable
|
||||
, tryGetModel
|
||||
) where
|
||||
|
||||
import SimpleSMT (SExpr)
|
||||
import qualified SimpleSMT as SMT
|
||||
@ -9,64 +35,229 @@ import qualified Data.Set as Set
|
||||
import Data.Maybe(catMaybes)
|
||||
import Data.List(partition)
|
||||
import Control.Monad(msum,zipWithM)
|
||||
import Data.Char(isSpace)
|
||||
import Text.Read(readMaybe)
|
||||
import qualified System.IO.Strict as StrictIO
|
||||
import System.FilePath((</>))
|
||||
import System.Directory(doesFileExist)
|
||||
|
||||
import Cryptol.Prelude(cryptolTcContents)
|
||||
import Cryptol.TypeCheck.Type
|
||||
import Cryptol.TypeCheck.Solver.CrySAT
|
||||
import Cryptol.TypeCheck.InferTypes
|
||||
import Cryptol.TypeCheck.TypePat hiding ((~>),(~~>))
|
||||
import Cryptol.TypeCheck.Subst(Subst, emptySubst, listSubst)
|
||||
import Cryptol.Utils.Panic
|
||||
import Cryptol.Utils.PP -- ( Doc )
|
||||
|
||||
|
||||
|
||||
-- | An SMT solver packed with a logger for debugging.
|
||||
data Solver = Solver
|
||||
{ solver :: SMT.Solver
|
||||
-- ^ The actual solver
|
||||
|
||||
, logger :: SMT.Logger
|
||||
-- ^ For debugging
|
||||
}
|
||||
|
||||
-- | Execute a computation with a fresh solver instance.
|
||||
withSolver :: SolverConfig -> (Solver -> IO a) -> IO a
|
||||
withSolver SolverConfig { .. } k =
|
||||
do logger <- if solverVerbose > 0 then SMT.newLogger 0 else return quietLogger
|
||||
|
||||
let smtDbg = if solverVerbose > 1 then Just logger else Nothing
|
||||
solver <- SMT.newSolver solverPath solverArgs smtDbg
|
||||
_ <- SMT.setOptionMaybe solver ":global-decls" "false"
|
||||
-- SMT.setLogic solver "QF_LIA"
|
||||
let sol = Solver { .. }
|
||||
loadTcPrelude sol solverPreludePath
|
||||
a <- k sol
|
||||
_ <- SMT.stop solver
|
||||
|
||||
return a
|
||||
|
||||
where
|
||||
quietLogger = SMT.Logger { SMT.logMessage = \_ -> return ()
|
||||
, SMT.logLevel = return 0
|
||||
, SMT.logSetLevel= \_ -> return ()
|
||||
, SMT.logTab = return ()
|
||||
, SMT.logUntab = return ()
|
||||
}
|
||||
|
||||
|
||||
-- | Load the definitions used for type checking.
|
||||
loadTcPrelude :: Solver -> [FilePath] {- ^ Search in this paths -} -> IO ()
|
||||
loadTcPrelude s [] = loadString s cryptolTcContents
|
||||
loadTcPrelude s (p : ps) =
|
||||
do let file = p </> "CryptolTC.z3"
|
||||
yes <- doesFileExist file
|
||||
if yes then loadFile s file
|
||||
else loadTcPrelude s ps
|
||||
|
||||
|
||||
loadFile :: Solver -> FilePath -> IO ()
|
||||
loadFile s file = loadString s =<< StrictIO.readFile file
|
||||
|
||||
loadString :: Solver -> String -> IO ()
|
||||
loadString s str = go (dropComments str)
|
||||
where
|
||||
go txt
|
||||
| all isSpace txt = return ()
|
||||
| otherwise =
|
||||
case SMT.readSExpr txt of
|
||||
Just (e,rest) -> SMT.command (solver s) e >> go rest
|
||||
Nothing -> panic "loadFile" [ "Failed to parse SMT file."
|
||||
, txt
|
||||
]
|
||||
|
||||
dropComments = unlines . map dropComment . lines
|
||||
dropComment xs = case break (== ';') xs of
|
||||
(as,_:_) -> as
|
||||
_ -> xs
|
||||
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Debugging
|
||||
|
||||
|
||||
debugBlock :: Solver -> String -> IO a -> IO a
|
||||
debugBlock s@Solver { .. } name m =
|
||||
do debugLog s name
|
||||
SMT.logTab logger
|
||||
a <- m
|
||||
SMT.logUntab logger
|
||||
return a
|
||||
|
||||
class DebugLog t where
|
||||
debugLog :: Solver -> t -> IO ()
|
||||
|
||||
debugLogList :: Solver -> [t] -> IO ()
|
||||
debugLogList s ts = case ts of
|
||||
[] -> debugLog s "(none)"
|
||||
_ -> mapM_ (debugLog s) ts
|
||||
|
||||
instance DebugLog Char where
|
||||
debugLog s x = SMT.logMessage (logger s) (show x)
|
||||
debugLogList s x = SMT.logMessage (logger s) x
|
||||
|
||||
instance DebugLog a => DebugLog [a] where
|
||||
debugLog = debugLogList
|
||||
|
||||
instance DebugLog a => DebugLog (Maybe a) where
|
||||
debugLog s x = case x of
|
||||
Nothing -> debugLog s "(nothing)"
|
||||
Just a -> debugLog s a
|
||||
|
||||
instance DebugLog Doc where
|
||||
debugLog s x = debugLog s (show x)
|
||||
|
||||
instance DebugLog Type where
|
||||
debugLog s x = debugLog s (pp x)
|
||||
|
||||
instance DebugLog Goal where
|
||||
debugLog s x = debugLog s (goal x)
|
||||
|
||||
instance DebugLog Subst where
|
||||
debugLog s x = debugLog s (pp x)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- | Returns goals that were not proved
|
||||
proveImp :: Solver -> [Prop] -> [Goal] -> IO [Goal]
|
||||
proveImp sol ps gs0 =
|
||||
debugBlock sol "PROVE IMP" $
|
||||
do let s = rawSolver sol
|
||||
let (gs,rest) = partition (isNumeric . goal) gs0
|
||||
numAsmp = filter isNumeric ps
|
||||
do let gs1 = concatMap flatGoal gs0
|
||||
(gs,rest) = partition (isNumeric . goal) gs1
|
||||
numAsmp = filter isNumeric (concatMap pSplitAnd ps)
|
||||
vs = Set.toList (fvs (numAsmp, map goal gs))
|
||||
tvs <- debugBlock sol "VARIABLES" $
|
||||
do SMT.push s
|
||||
Map.fromList <$> zipWithM (declareVar s) [ 0 .. ] vs
|
||||
do SMT.push (solver sol)
|
||||
Map.fromList <$> zipWithM (declareVar sol) [ 0 .. ] vs
|
||||
debugBlock sol "ASSUMPTIONS" $
|
||||
mapM_ (assume s tvs) numAsmp
|
||||
mapM_ (assume sol tvs) numAsmp
|
||||
gs' <- mapM (prove sol tvs) gs
|
||||
SMT.pop s
|
||||
SMT.pop (solver sol)
|
||||
return (catMaybes gs' ++ rest)
|
||||
|
||||
-- | Check if the given goals are known to be unsolvable.
|
||||
checkUnsolvable :: Solver -> [Goal] -> IO Bool
|
||||
checkUnsolvable sol gs0 =
|
||||
debugBlock sol "CHECK UNSOLVABLE" $
|
||||
do let s = rawSolver sol
|
||||
ps = filter isNumeric (map goal gs0)
|
||||
do let ps = filter isNumeric
|
||||
$ map goal
|
||||
$ concatMap flatGoal gs0
|
||||
vs = Set.toList (fvs ps)
|
||||
tvs <- debugBlock sol "VARIABLES" $
|
||||
do SMT.push s
|
||||
Map.fromList <$> zipWithM (declareVar s) [ 0 .. ] vs
|
||||
do push sol
|
||||
Map.fromList <$> zipWithM (declareVar sol) [ 0 .. ] vs
|
||||
ans <- unsolvable sol tvs ps
|
||||
SMT.pop s
|
||||
pop sol
|
||||
return ans
|
||||
|
||||
tryGetModel :: Solver -> [TVar] -> [Prop] -> IO (Maybe Subst)
|
||||
tryGetModel sol as ps =
|
||||
do push sol
|
||||
tvs <- Map.fromList <$> zipWithM (declareVar sol) [ 0 .. ] as
|
||||
mapM_ (assume sol tvs) ps
|
||||
sat <- SMT.check (solver sol)
|
||||
su <- case sat of
|
||||
SMT.Sat ->
|
||||
case as of
|
||||
[] -> return (Just emptySubst)
|
||||
_ -> do res <- SMT.getExprs (solver sol) (Map.elems tvs)
|
||||
let parse x = do e <- Map.lookup x tvs
|
||||
t <- parseNum =<< lookup e res
|
||||
return (x, t)
|
||||
return (listSubst <$> mapM parse as)
|
||||
_ -> return Nothing
|
||||
pop sol
|
||||
return su
|
||||
|
||||
where
|
||||
parseNum a
|
||||
| SMT.Other s <- a
|
||||
, SMT.List [con,val,isFin,isErr] <- s
|
||||
, SMT.Atom "mk-infnat" <- con
|
||||
, SMT.Atom "false" <- isErr
|
||||
, SMT.Atom fin <- isFin
|
||||
, SMT.Atom v <- val
|
||||
, Just n <- readMaybe v
|
||||
= Just (if fin == "false" then tInf else tNum (n :: Integer))
|
||||
|
||||
parseNum _ = Nothing
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
declareVar :: SMT.Solver -> Int -> TVar -> IO (TVar, SExpr)
|
||||
declareVar s x v = do let name = (if isFreeTV v then "fv" else "kv") ++ show x
|
||||
e <- SMT.declare s name cryInfNat
|
||||
SMT.assert s (SMT.fun "cryVar" [ e ])
|
||||
return (v,e)
|
||||
push :: Solver -> IO ()
|
||||
push sol = SMT.push (solver sol)
|
||||
|
||||
assume :: SMT.Solver -> TVars -> Prop -> IO ()
|
||||
assume s tvs p = SMT.assert s (SMT.fun "cryAssume" [ toSMT tvs p ])
|
||||
pop :: Solver -> IO ()
|
||||
pop sol = SMT.pop (solver sol)
|
||||
|
||||
|
||||
declareVar :: Solver -> Int -> TVar -> IO (TVar, SExpr)
|
||||
declareVar s x v =
|
||||
do let name = (if isFreeTV v then "fv" else "kv") ++ show x
|
||||
e <- SMT.declare (solver s) name cryInfNat
|
||||
SMT.assert (solver s) (SMT.fun "cryVar" [ e ])
|
||||
return (v,e)
|
||||
|
||||
assume :: Solver -> TVars -> Prop -> IO ()
|
||||
assume s tvs p = SMT.assert (solver s) (SMT.fun "cryAssume" [ toSMT tvs p ])
|
||||
|
||||
prove :: Solver -> TVars -> Goal -> IO (Maybe Goal)
|
||||
prove sol tvs g =
|
||||
debugBlock sol "PROVE" $
|
||||
do let s = rawSolver sol
|
||||
SMT.push s
|
||||
do let s = solver sol
|
||||
push sol
|
||||
SMT.assert s (SMT.fun "cryProve" [ toSMT tvs (goal g) ])
|
||||
res <- SMT.check s
|
||||
SMT.pop s
|
||||
pop sol
|
||||
case res of
|
||||
SMT.Unsat -> return Nothing
|
||||
_ -> return (Just g)
|
||||
@ -76,11 +267,10 @@ prove sol tvs g =
|
||||
unsolvable :: Solver -> TVars -> [Prop] -> IO Bool
|
||||
unsolvable sol tvs ps =
|
||||
debugBlock sol "UNSOLVABLE" $
|
||||
do let s = rawSolver sol
|
||||
SMT.push s
|
||||
mapM_ (assume s tvs) ps
|
||||
res <- SMT.check s
|
||||
SMT.pop s
|
||||
do SMT.push (solver sol)
|
||||
mapM_ (assume sol tvs) ps
|
||||
res <- SMT.check (solver sol)
|
||||
SMT.pop (solver sol)
|
||||
case res of
|
||||
SMT.Unsat -> return True
|
||||
_ -> return False
|
||||
@ -89,13 +279,18 @@ unsolvable sol tvs ps =
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- | Split up the 'And' in a goal
|
||||
flatGoal :: Goal -> [Goal]
|
||||
flatGoal g = [ g { goal = p } | p <- pSplitAnd (goal g) ]
|
||||
|
||||
|
||||
-- | Assumes no 'And'
|
||||
isNumeric :: Prop -> Bool
|
||||
isNumeric ty = matchDefault False
|
||||
$ msum [ is (|=|), is (|>=|), is aFin, is aTrue, andNum ty ]
|
||||
isNumeric ty = matchDefault False $ msum [ is (|=|), is (|>=|), is aFin ]
|
||||
where
|
||||
andNum t = anAdd t >>= \(x,y) -> return (isNumeric x && isNumeric y)
|
||||
is f = f ty >> return True
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
type TVars = Map TVar SExpr
|
||||
|
@ -1,4 +1,4 @@
|
||||
Loading module Cryptol
|
||||
(5, True).0 : {a} (fin a, a >= 3) => [a]
|
||||
((5, True), False).0 : {a} (fin a, a >= 3) => ([a], Bit)
|
||||
(((5, True), False).0).0 : {a} (fin a, a >= 3) => [a]
|
||||
(5, True).0 : {a} (a >= 3, fin a) => [a]
|
||||
((5, True), False).0 : {a} (a >= 3, fin a) => ([a], Bit)
|
||||
(((5, True), False).0).0 : {a} (a >= 3, fin a) => [a]
|
||||
|
@ -1,6 +1,6 @@
|
||||
Loading module Cryptol
|
||||
Assuming a = 5
|
||||
Assuming a = inf
|
||||
Assuming b = 5
|
||||
[[0x01, 0x02, 0x03, 0x04, 0x05], [0x06, 0x07, 0x08, 0x09, 0x0a],
|
||||
[0x0b, 0x0c, 0x0d, 0x0e, 0x0f], [0x10, 0x11, 0x12, 0x13, 0x14],
|
||||
[0x15, 0x16, 0x17, 0x18, 0x00]]
|
||||
[0x15, 0x16, 0x17, 0x18, 0x00], ...]
|
||||
|
@ -9,8 +9,8 @@ test03::test = \{a} (fin a, a >= width a) ->
|
||||
test03::foo a <> <>
|
||||
where
|
||||
/* Not recursive */
|
||||
test03::foo : {b} (fin b, b >= width a) => [b]
|
||||
test03::foo = \{b} (fin b, b >= width a) ->
|
||||
test03::foo : {b} (b >= width a, fin b) => [b]
|
||||
test03::foo = \{b} (b >= width a, fin b) ->
|
||||
Cryptol::demote a b <> <> <>
|
||||
|
||||
|
||||
|
@ -4,8 +4,8 @@ Loading module test04
|
||||
module test04
|
||||
import Cryptol
|
||||
/* Not recursive */
|
||||
test04::test : {a, b} (fin b, b >= 4) => a -> ((a, ()), (a, [b]))
|
||||
test04::test = \{a, b} (fin b, b >= 4) (a : a) ->
|
||||
test04::test : {a, b} (b >= 4, fin b) => a -> ((a, ()), (a, [b]))
|
||||
test04::test = \{a, b} (b >= 4, fin b) (a : a) ->
|
||||
(test04::f () (), test04::f [b] (Cryptol::demote 10 b <> <> <>))
|
||||
where
|
||||
/* Not recursive */
|
||||
|
@ -14,8 +14,8 @@ test05::foo : [10]
|
||||
test05::foo = Cryptol::demote 10 10 <> <> <>
|
||||
|
||||
/* Not recursive */
|
||||
test05::test : {a, b, c} (Zero b, fin c, c >= 4) => [a]b -> [c]
|
||||
test05::test = \{a, b, c} (Zero b, fin c, c >= 4) (a : [a]b) ->
|
||||
test05::test : {a, b, c} (Zero b, c >= 4, fin c) => [a]b -> [c]
|
||||
test05::test = \{a, b, c} (Zero b, c >= 4, fin c) (a : [a]b) ->
|
||||
Cryptol::demote 10 c <> <> <>
|
||||
where
|
||||
/* Not recursive */
|
||||
@ -60,8 +60,8 @@ test05::foo : [10]
|
||||
test05::foo = Cryptol::demote 10 10 <> <> <>
|
||||
|
||||
/* Not recursive */
|
||||
test05::test : {a, b, c} (Zero b, fin c, c >= 4) => [a]b -> [c]
|
||||
test05::test = \{a, b, c} (Zero b, fin c, c >= 4) (a : [a]b) ->
|
||||
test05::test : {a, b, c} (Zero b, c >= 4, fin c) => [a]b -> [c]
|
||||
test05::test = \{a, b, c} (Zero b, c >= 4, fin c) (a : [a]b) ->
|
||||
Cryptol::demote 10 c <> <> <>
|
||||
where
|
||||
/* Not recursive */
|
||||
|
Loading…
Reference in New Issue
Block a user