Redo the export to SMT story in a much simpler way.

This commit is contained in:
Iavor S. Diatchki 2017-02-16 16:46:38 -08:00
parent de770d5aed
commit 41131fe7ed
7 changed files with 431 additions and 157 deletions

View File

@ -14,7 +14,7 @@ Build-type: Simple
Cabal-version: >= 1.18
extra-source-files: bench/data/*.cry
data-files: *.cry Cryptol/*.cry
data-files: *.cry Cryptol/*.cry *.z3
data-dir: lib
source-repository head
@ -63,7 +63,7 @@ library
random >= 1.0.1,
sbv >= 5.15,
smtLib >= 1.0.7,
simple-smt >= 0.6.0,
simple-smt >= 0.7.0,
syb >= 0.4,
text >= 1.1,
template-haskell,
@ -127,6 +127,7 @@ library
Cryptol.TypeCheck.Sanity,
Cryptol.TypeCheck.Solver.Types,
Cryptol.TypeCheck.Solver.SMT,
Cryptol.TypeCheck.Solver.InfNat,
Cryptol.TypeCheck.Solver.Class,
Cryptol.TypeCheck.Solver.Selector,

217
lib/CryptolTC.z3 Normal file
View File

@ -0,0 +1,217 @@
(declare-datatypes ()
( (InfNat (mk-infnat (value Int) (isFin Bool) (isErr Bool)))
)
)
(declare-datatypes ()
( (MaybeBool (mk-mb (prop Bool) (isErrorProp Bool)))
)
)
(define-fun cryBool ((x Bool)) MaybeBool
(mk-mb x false)
)
(define-fun cryErrProp () MaybeBool
(mk-mb false true)
)
(define-fun cryInf () InfNat
(mk-infnat 0 false false)
)
(define-fun cryNat ((x Int)) InfNat
(mk-infnat x true false)
)
(define-fun cryErr () InfNat
(mk-infnat 0 false true)
)
; ------------------------------------------------------------
(define-fun cryEq ((x InfNat) (y InfNat)) MaybeBool
(ite (or (isErr x) (isErr y)) cryErrProp (cryBool
(ite (isFin x)
(ite (isFin y) (= (value x) (value y)) false)
(not (isFin y))
)))
)
(define-fun cryFin ((x InfNat)) MaybeBool
(ite (isErr x) cryErrProp (cryBool
(isFin x)))
)
(define-fun cryGeq ((x InfNat) (y InfNat)) MaybeBool
(ite (or (isErr x) (isErr y)) cryErrProp (cryBool
(ite (isFin x)
(ite (isFin y) (>= (value x) (value y)) false)
true
)))
)
(define-fun cryAnd ((x MaybeBool) (y MaybeBool)) MaybeBool
(ite (or (isErrorProp x) (isErrorProp y)) cryErrProp
(cryBool (and (prop x) (prop y)))
)
)
(define-fun cryTrue () MaybeBool
(cryBool true)
)
; -----------------------------------------------------
(define-fun cryVar ((x InfNat)) Bool
(and (not (isErr x)) (>= (value x) 0))
)
(define-fun cryAssume ((x MaybeBool)) Bool
(ite (isErrorProp x) true (prop x))
)
(declare-fun cryUnknown () Bool)
(define-fun cryProve ((x MaybeBool)) Bool
(ite (isErrorProp x) cryUnknown (not (prop x)))
)
; ------------------------------------------------------------
(define-fun cryAdd ((x InfNat) (y InfNat)) InfNat
(ite (or (isErr x) (isErr y)) cryErr
(ite (isFin x)
(ite (isFin y) (cryNat (+ (value x) (value y))) cryInf)
cryInf
))
)
(define-fun crySub ((x InfNat) (y InfNat)) InfNat
(ite (or (isErr x) (isErr y) (not (isFin y))) cryErr
(ite (isFin x)
(ite (>= (value x) (value y)) (cryNat (- (value x) (value y))) cryErr)
cryInf
))
)
(define-fun cryMul ((x InfNat) (y InfNat)) InfNat
(ite (or (isErr x) (isErr y)) cryErr
(ite (isFin x)
(ite (isFin y) (cryNat (* (value x) (value y)))
(ite (= (value x) 0) (cryNat 0) cryInf))
(ite (and (isFin y) (= (value y) 0)) (cryNat 0) cryInf)
))
)
(define-fun cryDiv ((x InfNat) (y InfNat)) InfNat
(ite (or (isErr x) (isErr y) (not (isFin x))) cryErr
(ite (isFin y)
(ite (= (value y) 0) cryErr (cryNat (div (value x) (value y))))
(cryNat 0)
))
)
(define-fun cryMod ((x InfNat) (y InfNat)) InfNat
(ite (or (isErr x) (isErr y) (not (isFin x))) cryErr
(ite (isFin y)
(ite (= (value y) 0) cryErr (cryNat (mod (value x) (value y))))
x
))
)
(define-fun cryMin ((x InfNat) (y InfNat)) InfNat
(ite (or (isErr x) (isErr y)) cryErr
(ite (isFin x)
(ite (isFin y)
(ite (<= (value x) (value y)) x y)
x)
y
))
)
(define-fun cryMax ((x InfNat) (y InfNat)) InfNat
(ite (or (isErr x) (isErr y)) cryErr
(ite (isFin x)
(ite (isFin y)
(ite (<= (value x) (value y)) y x)
y)
x
))
)
(declare-fun cryWidthUnknown (Int) Int)
(define-fun cryWidthTable ((x Int)) Int
(ite (< x 1) 0
(ite (< x 2) 1
(ite (< x 4) 2
(ite (< x 8) 3
(ite (< x 16) 4
(ite (< x 32) 5
(ite (< x 64) 6
(ite (< x 128) 7
(ite (< x 256) 8
(ite (< x 512) 9
(ite (< x 1024) 10
(cryWidthUnknown x))))))))))))
)
(define-fun cryWidth ((x InfNat)) InfNat
(ite (isErr x) cryErr
(ite (isFin x) (cryNat (cryWidthTable (value x)))
cryInf
))
)
(declare-fun cryExpUnknown (Int Int) Int)
(define-fun cryExpTable ((x Int) (y Int)) Int
(ite (= y 0) 1
(ite (= y 1) x
(ite (= x 0) 0
(cryExpUnknown x y))))
)
(define-fun cryExp ((x InfNat) (y InfNat)) InfNat
(ite (or (isErr x) (isErr y)) cryErr
(ite (isFin x)
(ite (isFin y)
(cryNat (cryExpTable (value x) (value y)))
(ite (< (value x) 2) x cryInf))
(ite (isFin y)
(ite (= (value y) 0) (cryNat 1) cryInf)
cryInf)
))
)
(define-fun cryLenFromThenTo ((x InfNat) (y InfNat) (z InfNat)) InfNat
(ite (or (isErr x) (not (isFin x))
(isErr y) (not (isFin y))
(isErr z) (not (isFin z))
(= (value x) (value y))) cryErr (cryNat
(ite (> (value x) (value y))
(ite (> (value z) (value x)) 0 (+ (div (- (value x) (value z))
(- (value x) (value y))) 1))
(ite (< (value z) (value x)) 0 (+ (div (- (value z) (value x))
(- (value y) (value x))) 1))
)))
)
(define-fun cryLenFromThen ((x InfNat) (y InfNat) (z InfNat)) InfNat
(ite (or (isErr x) (not (isFin x))
(isErr y) (not (isFin y))
(isErr z) (not (isFin z))
(= (value x) (value y))) cryErr
(ite (< (value y) (value x)) (cryLenFromThenTo x y (cryNat 0))
(cryLenFromThenTo x y (cryNat (- (cryExpTable 2 (value z)) 1))))
)
)
; -------------------

View File

@ -434,6 +434,7 @@ genInferInput r prims env = do
monoBinds <- getMonoBinds
cfg <- getSolverConfig
supply <- getSupply
searchPath <- getSearchPath
-- TODO: include the environment needed by the module
return T.InferInput
@ -444,6 +445,7 @@ genInferInput r prims env = do
, T.inpNameSeeds = seeds
, T.inpMonoBinds = monoBinds
, T.inpSolverConfig = cfg
, T.inpSearchPath = searchPath
, T.inpSupply = supply
, T.inpPrimNames = prims
}

View File

@ -41,6 +41,10 @@ import MonadLib hiding (mapM)
import Data.IORef
import System.FilePath((</>))
import System.Directory(doesFileExist)
import GHC.Generics (Generic)
import Control.DeepSeq
@ -59,6 +63,8 @@ data InferInput = InferInput
-- signatures be monomorphized?
, inpSolverConfig :: SolverConfig -- ^ Options for the constraint solver
, inpSearchPath :: [FilePath]
-- ^ Where to look for Cryptol theory file.
, inpPrimNames :: !PrimMap -- ^ The mapping from 'Ident' to 'Name',
-- for names that the typechecker
@ -96,7 +102,8 @@ bumpCounter = do RO { .. } <- IM ask
runInferM :: TVars a => InferInput -> InferM a -> IO (InferOutput a)
runInferM info (IM m) = CrySAT.withSolver (inpSolverConfig info) $ \solver ->
do coutner <- newIORef 0
do loadCryTCPrel solver (inpSearchPath info)
coutner <- newIORef 0
rec ro <- return RO { iRange = inpRange info
, iVars = Map.map ExtVar (inpVars info)
, iTVars = []
@ -160,6 +167,14 @@ 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 _ [] = panic "Failed to find file" [] -- return ()
loadCryTCPrel s (p : ps) =
do let file = p </> "CryptolTC.z3"
yes <- doesFileExist file
if yes then CrySAT.loadFile s file
else loadCryTCPrel s ps

View File

@ -17,7 +17,6 @@ module Cryptol.TypeCheck.Solve
, defaultReplExpr
) where
import Cryptol.Parser.Position (emptyRange)
import Cryptol.TypeCheck.PP(pp)
import Cryptol.TypeCheck.AST
import Cryptol.TypeCheck.Monad
@ -31,17 +30,18 @@ import Cryptol.TypeCheck.Solver.Selector(tryHasGoal)
import Cryptol.TypeCheck.SimpType(tMax)
import Cryptol.TypeCheck.Solver.SMT(proveImp)
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 (debugBlock, DebugLog(..))
import Cryptol.TypeCheck.Solver.CrySAT
import Cryptol.Utils.PP (text,vcat,(<+>))
import Cryptol.Utils.Panic(panic)
import Cryptol.Utils.Patterns(matchMaybe)
import Control.Monad (unless, guard, mzero)
import Control.Monad (guard, mzero)
import Control.Applicative ((<|>))
import Data.Either(partitionEithers)
import Data.Maybe(catMaybes)
@ -180,26 +180,49 @@ proveImplicationIO s f varsInEnv ps asmps0 gs0 =
do let ctxt = assumptionIntervals Map.empty asmps
res <- quickSolverIO ctxt gs
case res of
Left err -> return (Left (UnsolvedGoal True err), emptySubst)
Left bad -> return (Left (UnsolvedGoal True bad), emptySubst)
Right (su,[]) -> return (Right [], su)
Right (su,gs1) ->
do (res3,su1) <- proveImplicationWithExtSolver
s f varsInEnv ps asmps gs1
case res3 of
Left err -> return (Left (cleanupError err), emptySubst)
Right ws -> return (Right ws, su1 @@ su)
do gs2 <- proveImp s asmps gs1
case gs2 of
[] -> return (Right [], su)
gs3 ->
do let free = Set.toList
$ Set.difference (fvs (map goal gs3)) varsInEnv
case improveByDefaultingWithPure free gs3 of
(_,_,newSu,_)
| isEmptySubst newSu -> return (err gs3, su) -- XXX: Old?
(_,newGs,newSu,ws) ->
do let su1 = newSu @@ su
(res1,su2) <- proveImplicationIO s f varsInEnv ps
(apSubst su1 asmps0) newGs
let su3 = su2 @@ su1
case res1 of
Left bad -> return (Left bad, su3)
Right ws1 -> return (Right (ws++ws1),su3)
where
err us = Left $ cleanupError
$ UnsolvedDelayedCt
$ DelayedCt { dctSource = f
, dctForall = ps
, dctAsmps = asmps0
, dctGoals = us
}
(asmps,gs) =
case matchMaybe (improveProps True Map.empty asmps0) of
Nothing -> (asmps0,gs0)
Just (newSu,newAsmps) ->
( [ TVar x =#= t | (x,t) <- substToList newSu ]
++ newAsmps
, [ g { goal = apSubst newSu (goal g) } | g <- gs0
, not (goal g `elem` asmps0) ]
)
let gs1 = [ g { goal = p } | g <- gs0, p <- pSplitAnd (goal g)
, notElem p asmps0 ]
in case matchMaybe (improveProps True Map.empty asmps0) of
Nothing -> (asmps0,gs1)
Just (newSu,newAsmps) ->
( [ TVar x =#= t | (x,t) <- substToList newSu ]
++ newAsmps
, [ g { goal = apSubst newSu (goal g) } | g <- gs1 ]
)
cleanupError :: Error -> Error
@ -216,118 +239,7 @@ cleanupError err =
proveImplicationWithExtSolver
:: Num.Solver
-> Name -- ^ Checking this function
-> Set TVar -- ^ These appear in the environment, and we should
-- not try to default the
-> [TParam] -- ^ Type parameters
-> [Prop] -- ^ Assumed constraint
-> [Goal] -- ^ Collected constraints
-> IO (Either Error [Warning], Subst)
proveImplicationWithExtSolver s lname varsInEnv as ps gs =
debugBlock s "proveImplicationIO" $
do debugBlock s "assumes" (debugLog s ps)
debugBlock s "shows" (debugLog s gs)
debugLog s "1. ------------------"
_simpPs <- Num.assumeProps s ps
mbImps <- Num.check s
debugLog s "2. ------------------"
case mbImps of
Nothing ->
do debugLog s "(contradiction in assumptions)"
return (Left $ UnusableFunction lname ps, emptySubst)
Just (imps,extra) ->
do let su = importImps imps
gs0 = apSubst su gs
debugBlock s "improvement from assumptions:" $ debugLog s su
let (scs,invalid) = importSideConds extra
unless (null invalid) $
panic "proveImplicationIO" ( "Unable to import all side conditions:"
: map (show . Num.ppProp) invalid )
let gs1 = filter ((`notElem` ps) . goal) gs0
debugLog s "3. ---------------------"
let ctxt = assumptionIntervals Map.empty ps
(mb,su1) <- simpGoals' s ctxt (scs ++ gs1)
case mb of
Left badGs -> reportUnsolved badGs (su1 @@ su)
Right [] -> return (Right [], su1 @@ su)
Right us ->
-- Last hope: try to default stuff
do let vs = Set.filter isFreeTV $ fvs $ map goal us
dVars = Set.toList (vs `Set.difference` varsInEnv)
-- (_,us1,su2,ws) <- improveByDefaultingWith s dVars us
(_,us1,su2,ws) = improveByDefaultingWithPure dVars us
Right (su3,us2) <- quickSolverIO ctxt us1
case us2 of
[] -> return (Right ws, su3 @@ su2 @@ su1 @@ su)
_ -> reportUnsolved us1 (su3 @@ su2 @@ su1 @@ su)
where
reportUnsolved us su =
return ( Left $ UnsolvedDelayedCt
$ DelayedCt { dctSource = lname
, dctForall = as
, dctAsmps = ps
, dctGoals = us
}, su)
{- Constraints and satisfiability:
1. [Satisfiable] A collection of constraints is _satisfiable_, if there is an
assignment for the variables that make all constraints true.
2. [Valid] If a constraint is satisfiable for any assignment of its free
variables, then it is _valid_, and may be ommited.
3. [Partial] A constraint may _partial_, which means that under some
assignment it is neither true nor false. For example:
`x - y > 5` is true for `{ x = 15, y = 3 }`, it is false for
`{ x = 5, y = 4 }`, and it is neither for `{ x = 1, y = 2 }`.
Note that constraints that are always true or undefined are NOT
valid, as there are assignemntes for which they are not true.
An example of such constraint is `x - y >= 0`.
4. [Provability] Instead of thinking of three possible values for
satisfiability (i.e., true, false, and unknown), we could instead
think of asking: "Is constraint C provable". This essentailly
maps "true" to "true", and "false,unknown" to "false", if we
treat constraints with malformed parameters as unprovable.
-}
{-
The plan:
1. Start with a set of constraints, CS
2. Compute its well-defined closure, DS.
3. Simplify constraints: evaluate terms in constraints as much as possible
4. Solve: eliminate constraints that are true
5. Check for consistency
6. Compute improvements
7. For each type in the improvements, add well-defined constraints
8. Instantiate constraints with substitution
9. Goto 3
-}
simpGoals' :: Num.Solver -> Ctxt -> [Goal] -> IO (Either [Goal] [Goal], Subst)
simpGoals' s asmps gs0 = go emptySubst [] (wellFormed gs0 ++ gs0)
@ -490,29 +402,6 @@ importSplitImps = mk . partitionEithers . map imp . Map.toList
-- | Import an improving substitution into a Cryptol substitution.
-- The substitution will contain both unification and skolem variables,
-- so this should be used when processing *givens*.
importImps :: Map Num.Name Num.Expr -> Subst
importImps = listSubst . map imp . Map.toList
where
imp (x,e) = case (x, Num.importType e) of
(Num.UserName tv, Just ty) -> (tv,ty)
_ -> panic "importImps" [ "Failed to import:", show x, show e ]
importSideConds :: [Num.Prop] -> ([Goal],[Num.Prop])
importSideConds = go [] []
where
go ok bad [] = ([ Goal CtImprovement emptyRange g | g <- ok], bad)
go ok bad (p:ps) = case Num.importProp p of
Just p' -> go (p' ++ ok) bad ps
Nothing -> go ok (p:bad) ps
--------------------------------------------------------------------------------

View File

@ -21,6 +21,8 @@ module Cryptol.TypeCheck.Solver.CrySAT
, DebugLog(..)
, knownDefined, numericRight
, minimizeContradictionSimpDef
, loadFile
, rawSolver
) where
import qualified Cryptol.TypeCheck.AST as Cry
@ -282,6 +284,19 @@ data Solver = Solver
-- ^ For debugging
}
loadFile :: Solver -> FilePath -> IO ()
loadFile s file = do -- txt <- readFile file
-- mapM_ putStrLn (lines txt)
go =<< readFile file
where
go txt = case SMT.readSExpr txt of
Just (e,rest) -> SMT.command (solver s) e >> go rest
Nothing -> return ()
rawSolver :: Solver -> SMT.Solver
rawSolver = solver
-- | Keeps track of declared variables and non-linear terms.
data VarInfo = VarInfo
@ -418,7 +433,7 @@ withSolver SolverConfig { .. } k =
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"
-- SMT.setLogic solver "QF_LIA"
declared <- newIORef viEmpty
a <- k Solver { .. }
_ <- SMT.stop solver

View File

@ -0,0 +1,135 @@
{-# Language FlexibleInstances #-}
module Cryptol.TypeCheck.Solver.SMT (proveImp) where
import SimpleSMT (SExpr)
import qualified SimpleSMT as SMT
import Data.Map ( Map )
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Maybe(catMaybes)
import Data.List(partition)
import Control.Monad(msum,zipWithM)
import Cryptol.TypeCheck.Type
import Cryptol.TypeCheck.Solver.CrySAT
import Cryptol.TypeCheck.InferTypes
import Cryptol.TypeCheck.TypePat hiding ((~>),(~~>))
import Cryptol.Utils.Panic
-- | 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
vs = Set.toList (fvs (numAsmp, map goal gs))
tvs <- debugBlock sol "VARIABLES" $
do SMT.push s
Map.fromList <$> zipWithM (declareVar s) [ 0 .. ] vs
debugBlock sol "ASSUMPTIONS" $
mapM_ (assume s tvs) numAsmp
gs' <- mapM (prove sol tvs) gs
SMT.pop s
return (catMaybes gs' ++ rest)
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)
assume :: SMT.Solver -> TVars -> Prop -> IO ()
assume s tvs p = SMT.assert 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
SMT.assert s (SMT.fun "cryProve" [ toSMT tvs (goal g) ])
res <- SMT.check s
SMT.pop s
case res of
SMT.Unsat -> return Nothing
_ -> return (Just g)
--------------------------------------------------------------------------------
isNumeric :: Prop -> Bool
isNumeric ty = matchDefault False
$ msum [ is (|=|), is (|>=|), is aFin, is aTrue, andNum ty ]
where
andNum t = anAdd t >>= \(x,y) -> return (isNumeric x && isNumeric y)
is f = f ty >> return True
--------------------------------------------------------------------------------
type TVars = Map TVar SExpr
cryInfNat :: SExpr
cryInfNat = SMT.const "InfNat"
toSMT :: TVars -> Type -> SExpr
toSMT tvs ty = matchDefault (panic "toSMT" [ "Unexpected type", show ty ])
$ msum $ map (\f -> f tvs ty)
[ aInf ~> "cryInf"
, aNat ~> "cryNat"
, aFin ~> "cryFin"
, (|=|) ~> "cryEq"
, (|>=|) ~> "cryGeq"
, aAnd ~> "cryAnd"
, aTrue ~> "cryTrue"
, anAdd ~> "cryAdd"
, (|-|) ~> "crySub"
, aMul ~> "cryMul"
, (|^|) ~> "cryExp"
, (|/|) ~> "cryDiv"
, (|%|) ~> "cryMod"
, aMin ~> "cryMin"
, aMax ~> "cryMax"
, aWidth ~> "cryWidth"
, aLenFromThen ~> "cryLenFromThen"
, aLenFromThenTo ~> "cryLenFromThenTo"
, aTVar ~> "(unused)"
]
--------------------------------------------------------------------------------
(~>) :: Mk a => (Type -> Match a) -> String -> TVars -> Type -> Match SExpr
(m ~> f) tvs t = m t >>= \a -> return (mk tvs f a)
class Mk t where
mk :: TVars -> String -> t -> SExpr
instance Mk () where
mk _ f _ = SMT.const f
instance Mk Integer where
mk _ f x = SMT.fun f [ SMT.int x ]
instance Mk TVar where
mk tvs _ x = tvs Map.! x
instance Mk Type where
mk tvs f x = SMT.fun f [toSMT tvs x]
instance Mk (Type,Type) where
mk tvs f (x,y) = SMT.fun f [ toSMT tvs x, toSMT tvs y]
instance Mk (Type,Type,Type) where
mk tvs f (x,y,z) = SMT.fun f [ toSMT tvs x, toSMT tvs y, toSMT tvs z ]
--------------------------------------------------------------------------------