diff options
-rw-r--r-- | compiler/typecheck/TcInteract.lhs | 118 | ||||
-rw-r--r-- | compiler/typecheck/TcSMonad.lhs | 10 | ||||
-rw-r--r-- | compiler/typecheck/TcTypeNats.hs | 765 |
3 files changed, 881 insertions, 12 deletions
diff --git a/compiler/typecheck/TcInteract.lhs b/compiler/typecheck/TcInteract.lhs index 42e04650c1..02c5866018 100644 --- a/compiler/typecheck/TcInteract.lhs +++ b/compiler/typecheck/TcInteract.lhs @@ -14,7 +14,6 @@ import VarSet import Type import Unify import InstEnv( lookupInstEnv, instanceDFunId ) -import CoAxiom(sfInteractTop, sfInteractInert) import Var import TcType @@ -33,6 +32,7 @@ import FamInstEnv ( FamInstEnvs, instNewTyConTF_maybe ) import TcEvidence import Outputable +import TcTypeNats import TcRnTypes import TcErrors import TcSMonad @@ -50,6 +50,8 @@ import Unique( hasKey ) import FastString ( sLit, fsLit ) import DynFlags import Util + +import System.IO(hPutStrLn,stderr) \end{code} ********************************************************************** @@ -87,9 +89,9 @@ solveInteractGiven loc old_fsks givens | null givens -- Shortcut for common case = return (True, old_fsks) | otherwise - = do { implics1 <- solveInteract fsk_bag + = do { implics1 <- solveInteractWithExtern True fsk_bag - ; (no_eqs, more_fsks, implics2) <- getGivenInfo (solveInteract given_bag) + ; (no_eqs, more_fsks, implics2) <- getGivenInfo (solveInteractWithExtern True given_bag) ; MASSERT( isEmptyBag implics1 && isEmptyBag implics2 ) -- empty implics because we discard Given equalities between -- foralls (see Note [Do not decompose given polytype equalities] @@ -115,11 +117,16 @@ solveInteractGiven loc old_fsks givens -- The main solver loop implements Note [Basic Simplifier Plan] --------------------------------------------------------------- + solveInteract :: Cts -> TcS (Bag Implication) +solveInteract = solveInteractWithExtern False + +solveInteractWithExtern :: Bool -- ^ Are we in the given stage? + -> Cts -> TcS (Bag Implication) -- Returns the final InertSet in TcS -- Has no effect on work-list or residual-iplications -solveInteract cts - = {-# SCC "solveInteract" #-} +solveInteractWithExtern inGivenStage cts + = {-# SCC "solveInteractWithExtern" #-} withWorkList cts $ do { dyn_flags <- getDynFlags ; solve_loop (maxSubGoalDepth dyn_flags) } @@ -129,12 +136,111 @@ solveInteract cts do { sel <- selectNextWorkItem max_depth ; case sel of NoWorkRemaining -- Done, successfuly (modulo frozen) - -> return () + -> do moreWork <- interactExternSolver inGivenStage + if moreWork then solve_loop max_depth + else return () MaxDepthExceeded cnt ct -- Failure, depth exceeded -> wrapErrTcS $ solverDepthErrorTcS cnt (ctEvidence ct) NextWorkItem ct -- More work, loop around! -> do { runSolverPipeline thePipeline ct; solve_loop max_depth } } +interactExternSolver :: Bool -- ^ Are we in given stage? + -> TcS Bool -- ^ Did we generatew new work. +interactExternSolver inGivenStage = + do extSol $ hPutStrLn stderr $ "=== EXT SOL: " ++ (if inGivenStage then "[G]" else "[W/D]") + iSet <- getTcSInerts + let iCans = inert_cans iSet + + relevantCt + | inGivenStage = isGivenCt + | otherwise = not . isGivenCt + + (relFeqs, otherFeqs) = partitionFunEqs relevantCt (inert_funeqs iCans) + relList = bagToList relFeqs + othersList = funEqsToList otherFeqs + + eqs = inert_eqs iCans + + {- `survived` should be a sub-set of the inert funeqs. + This function rebuilds the inert set, after we've remove a constraint + (e.g., because they were solved, or caused a contradiciton. -} + + rebuildInerts survived = + do let new_feqs = foldr addCt otherFeqs survived + new_iCans = iCans { inert_funeqs = new_feqs } + setTcSInerts iSet { inert_cans = new_iCans } + + -- Sanity checking: + when (inGivenStage && not (null othersList)) $ + panicTcS (text "Non given constraints in given stage" $$ + (vcat $ map ppr othersList)) + + + withSol $ \sol -> + do extSol $ extSolAssert sol othersList + + + res <- extSol $ extSolImprove sol inGivenStage relList + case res of + + -- Kick-out constraints that lead to a contradiciton + -- and add them as insoluable. + ExtSolContradiction bad_feqs ok_feqs -> + do rebuildInerts ok_feqs + mapM_ emitInsoluble bad_feqs + return False + + -- Consistent + ExtSolOk newWork + + -- We found some new work to do. + | let reallyNew + | inGivenStage = newWork + | otherwise = filter (notKnownEq eqs) newWork + , not (null reallyNew) -> + do updWorkListTcS (extendWorkListEqs reallyNew) + return True + + -- Nothing else to do. + | inGivenStage -> return False + + -- No new work, try to solve wanted constraints. + | otherwise -> + do let (wanteds, derived) = partition isWantedCt relList + (solved, unsolved) <- extSol $ extSolSolve sol wanteds + case solved of + [] -> return False -- Shortcut for common case. + _ -> do rebuildInerts (unsolved ++ derived) + let setEv (ev,ct) = + setEvBind (ctev_evar (cc_ev ct)) ev + mapM_ setEv solved + return False + + + where + notKnownEq eqs ct = + case getEqPredTys_maybe (ctPred ct) of + Just (_,tvt,ty) + | Just tv <- getTyVar_maybe tvt -> + all (not . tcEqType ty) + [ t | CTyEqCan { cc_rhs = t } <- findTyEqs eqs tv ] + _ -> True + + + addCt ct mp = + case ct of + CFunEqCan { cc_fun = tc, cc_tyargs = tys } -> addFunEq mp tc tys ct + _ -> panic "Not FunEq constraint while rebuilding external work." + + withSol k = do s <- extSol (newExternalSolver "cvc4" + [ "--incremental", "--lang=smtlib2" ]) + + a <- k s + extSol (extSolStop s) + return a + + + type WorkItem = Ct type SimplifierStage = WorkItem -> TcS StopOrContinue diff --git a/compiler/typecheck/TcSMonad.lhs b/compiler/typecheck/TcSMonad.lhs index 898e2b8c13..60ff5d26c8 100644 --- a/compiler/typecheck/TcSMonad.lhs +++ b/compiler/typecheck/TcSMonad.lhs @@ -76,6 +76,7 @@ module TcSMonad ( findFunEq, findTyEqs, findDict, findDictsByClass, addDict, addDictsByClass, delDict, partitionDicts, findFunEqsByTyCon, findFunEqs, addFunEq, replaceFunEqs, partitionFunEqs, + emptyFunEqs, funEqsToList, instDFunType, -- Instantiation newFlexiTcSTy, instFlexiTcS, instFlexiTcSHelperTcS, @@ -84,6 +85,8 @@ module TcSMonad ( Untouchables, isTouchableMetaTyVarTcS, isFilledMetaTyVar_maybe, zonkTyVarsAndFV, + extSol, + getDefaultInfo, getDynFlags, getGlobalRdrEnvTcS, matchFam, matchOpenFam, @@ -1947,3 +1950,10 @@ deferTcSForAllEq role loc (tvs1,body1) (tvs2,body2) } \end{code} +Interaction with an External SMT Solver +--------------------------------------- + +\begin{code} +extSol :: IO a -> TcS a +extSol m = TcS $ \_ -> liftIO m +\end{code} diff --git a/compiler/typecheck/TcTypeNats.hs b/compiler/typecheck/TcTypeNats.hs index 37fc6e0cdb..bd60f8f053 100644 --- a/compiler/typecheck/TcTypeNats.hs +++ b/compiler/typecheck/TcTypeNats.hs @@ -2,17 +2,22 @@ module TcTypeNats ( typeNatTyCons , typeNatCoAxiomRules , BuiltInSynFamily(..) + , ExternalSolver(..), ExtSolRes(..), newExternalSolver ) where import Type import Pair import TcType ( TcType, tcEqType ) +import TcEvidence ( mkTcAxiomRuleCo, EvTerm(..) ) import TyCon ( TyCon, SynTyConRhs(..), mkSynTyCon, TyConParent(..) ) import Coercion ( Role(..) ) -import TcRnTypes ( Xi ) +import TcRnTypes ( Xi, Ct(..), ctPred, CtEvidence(..), mkNonCanonical + , CtLoc, ctLoc, isGivenCt ) import CoAxiom ( CoAxiomRule(..), BuiltInSynFamily(..) ) -import Name ( Name, BuiltInSyntax(..) ) -import TysWiredIn ( typeNatKind, typeSymbolKind +import Name ( Name, BuiltInSyntax(..), nameOccName, nameUnique ) +import OccName ( occNameString ) +import Var ( tyVarName, tyVarKind ) +import TysWiredIn ( typeNatKind, typeSymbolKind, typeNatKindCon , mkWiredInTyConName , promotedBoolTyCon , promotedFalseDataCon, promotedTrueDataCon @@ -31,9 +36,24 @@ import PrelNames ( gHC_TYPELITS , typeNatCmpTyFamNameKey , typeSymbolCmpTyFamNameKey ) +import Panic (panic) import FastString ( FastString, fsLit ) +import UniqFM ( UniqFM, emptyUFM, unitUFM, plusUFM, eltsUFM ) +import Data.Map (Map) import qualified Data.Map as Map -import Data.Maybe ( isJust ) +import Data.Maybe ( isJust, mapMaybe ) +import Data.Char ( isSpace ) +import Data.List ( unfoldr, foldl', partition, sortBy, groupBy ) +import Data.IORef ( IORef, newIORef, atomicModifyIORef', + atomicModifyIORef, modifyIORef' + , readIORef + ) +import Control.Monad ( forever, msum ) +import Control.Concurrent ( forkIO ) +import qualified Control.Exception as X +import System.Process ( runInteractiveProcess, waitForProcess ) +import System.IO ( hPutStrLn, hFlush, hGetContents, hGetLine, + stderr ) {------------------------------------------------------------------------------- Built-in type constructors for functions on type-lelve nats @@ -270,9 +290,27 @@ typeNatCoAxiomRules = Map.fromList $ map (\x -> (coaxrName x, x)) ] +decisionProcedure :: String -> CoAxiomRule +decisionProcedure name = + CoAxiomRule + { coaxrName = fsLit name + , coaxrTypeArity = 2 + , coaxrAsmpRoles = [] + , coaxrRole = Nominal + , coaxrProves = \ts cs -> + case (ts,cs) of + ([s,t],[]) -> return (s === t) + _ -> Nothing + } -{------------------------------------------------------------------------------- -Various utilities for making axioms and types + +evBySMT :: String -> (Type, Type) -> EvTerm +evBySMT name (t1,t2) = + EvCoercion $ mkTcAxiomRuleCo (decisionProcedure name) [t1,t2] [] + + + +-- Various utilities for making axioms and types -------------------------------------------------------------------------------} (.+.) :: Type -> Type -> Type @@ -681,7 +719,722 @@ genLog x base = Just (exactLoop 0 x) +-------------------------------------------------------------------------------- +-- Interface + +data ExternalSolver = ExternalSolver + { extSolAssert :: [Ct] -> IO () + -- ^ Add some assertions. + -- Changes assertions. + + , extSolImprove :: Bool {- are we in the given stage -} + -> [Ct] -> IO ExtSolRes + -- ^ Check for consistency and new work. + -- Does not change assertions. + + , extSolSolve :: [Ct] -> IO ([(EvTerm,Ct)], [Ct]) + -- ^ Solve some constraints. + -- Does not change assertions. + + , extSolStop :: IO () + -- ^ Exit the solver. + -- The solver should not be used after this is called. + } + +data ExtSolRes + = ExtSolContradiction {- inconsistent -} [Ct] + {- all others -} [Ct] + -- ^ There is no model for assertions. + + | ExtSolOk [Ct] + -- ^ New work (facts that will hold in all models) + +-------------------------------------------------------------------------------- +-- Concrete implementation + +newExternalSolver :: FilePath -> [String] -> IO ExternalSolver +newExternalSolver exe opts = + do proc <- startSolverProcess exe opts + + -- Prep the solver + solverSimpleCmd proc [ "set-option", ":print-success", "true" ] + solverSimpleCmd proc [ "set-option", ":produce-models", "true" ] + solverSimpleCmd proc [ "set-logic", "QF_LIA" ] + + viRef <- newIORef emptyVarInfo + + let dbg = solverDebug proc + + return ExternalSolver + { extSolAssert = \cts -> + do dbg "=== ASSERT ===" + (_,ours) <- solverPrepare proc viRef cts + let expr (_,e,_) = e + mapM_ (solverAssume proc . expr) ours + mapM_ (solverDebug proc . show . expr) ours + + , extSolImprove = \b cts -> do dbg "=== IMPROVE ===" + solverImprove proc viRef b cts + + , extSolSolve = \cts -> do dbg "=== SOLVE ===" + solverSimplify proc viRef cts + + , extSolStop = solverStop proc + } + +mkNewFact :: CtLoc -> Bool -> (Type,Type) -> CtEvidence +mkNewFact newLoc withEv (t1,t2) + | withEv = CtGiven { ctev_pred = newPred + , ctev_evtm = evBySMT "SMT" (t1,t2) + , ctev_loc = newLoc + } + | otherwise = CtDerived { ctev_pred = newPred + , ctev_loc = newLoc + } + where + newPred = mkEqPred t1 t2 + +-- A command with no interesting result. +solverAckCmd :: SolverProcess -> SExpr -> IO () +solverAckCmd proc c = + do res <- solverDo proc c + case res of + SAtom "success" -> return () + _ -> fail $ unlines + [ "Unexpected result from the SMT solver:" + , " Expected: success" + , " Result: " ++ renderSExpr res "" + ] + +-- A command entirely made out of atoms, with no interesting result. +solverSimpleCmd :: SolverProcess -> [String] -> IO () +solverSimpleCmd proc = solverAckCmd proc . SList . map SAtom + + + +-- Checkpoint state +solverPush :: SolverProcess -> IORef VarInfo -> IO () +solverPush proc viRef = + do solverSimpleCmd proc [ "push", "1" ] + modifyIORef' viRef startScope + +-- Restore to last check-point +solverPop :: SolverProcess -> IORef VarInfo -> IO () +solverPop proc viRef = + do solverSimpleCmd proc [ "pop", "1" ] + modifyIORef' viRef endScope + + +-- Assume a fact +solverAssume :: SolverProcess -> SExpr -> IO () +solverAssume proc e = solverAckCmd proc $ SList [ SAtom "assert", e ] + +-- Declare a new variable +solverDeclare :: SolverProcess -> IORef VarInfo -> (TyVar, String, Ty) -> IO () +solverDeclare proc viRef (tv,x,ty) = + do status <- atomicModifyIORef' viRef (declareVar tv) + case status of + Declared -> return () + Undeclared -> + do solverAckCmd proc $ + SList [SAtom "declare-fun", SAtom x, SList [], smtTy ty] + mapM_ (solverAssume proc) (smtExtraConstraints x ty) + +data SmtRes = Unsat | Unknown | Sat + +-- Check if assumptions are consistent. Does not return a model. +solverCheck :: SolverProcess -> IO SmtRes +solverCheck proc = + do res <- solverDo proc (SList [ SAtom "check-sat" ]) + case res of + SAtom "unsat" -> return Unsat + SAtom "unknown" -> return Unknown + SAtom "sat" -> return Sat + _ -> fail $ unlines + [ "Unexpected result from the SMT solver:" + , " Expected: unsat, unknown, or sat" + , " Result: " ++ renderSExpr res "" + ] + +-- Prove something by concluding that a counter-example is impossible. +solverProve :: SolverProcess -> IORef VarInfo -> SExpr -> IO Bool +solverProve proc viRef e = + debug $ + do solverPush proc viRef + solverAssume proc $ SList [ SAtom "not", e ] + res <- solverCheck proc + solverPop proc viRef + case res of + Unsat -> return True + _ -> return False + + where + debug m = do solverDebug proc ("Prove: " ++ show e) + solverDebugNext proc + res <- m + solverDebug proc (show res) + solverDebugPrev proc + return res + +-- Get values for the variables that are in scope. +solverGetModel :: SolverProcess -> VarInfo -> IO [(String,SExpr)] +solverGetModel proc vi = + do res <- solverDo proc + $ SList [ SAtom "get-value", SList [ SAtom v | v <- inScope vi ] ] + case res of + SList xs -> return [ (x,v) | SList [ SAtom x, v ] <- xs ] + _ -> fail $ unlines + [ "Unexpected response from the SMT solver:" + , " Exptected: a list" + , " Result: " ++ renderSExpr res "" + ] + +{- Try to generalize some facts for a model. + +In particular, we look for facts of the form: + * x = K, where `K` is a constant, and + * x = y, where `y` is a variable. + +Returns only the new facts. +-} +solverImproveModel :: SolverProcess -> IORef VarInfo -> + [(String,SExpr)] -> IO [ (String,SExpr) ] +solverImproveModel proc viRef imps = + do xs <- constEq [] imps + solverDebug proc $ "Improvements: " ++ unwords [ x ++ " = " ++ renderSExpr e "," | (x,e) <- xs ] + return xs + where + + constEq imps [] = return imps + constEq imps ((x,v) : more) = + -- First we check if this is the only possible concrete value for `x`: + do proved <- solverProve proc viRef (SList [ SAtom "=", SAtom x, v ]) + if proved + then constEq ((x,v) : imps) more + -- If two variables `x` and `y` have the same value in this model, + -- then we check if they must be equal in all models. + else let (candidates,others) = partition ((== v) . snd) more + in varEq x imps others candidates + + + varEq _ imps more [] = constEq imps more + varEq x imps more (def@(y,_) : ys) = + do let e = SAtom y + -- Check if `x` and `y` must be the same in all models. + proved <- solverProve proc viRef (SList [ SAtom "=", SAtom x, e ]) + if proved + then varEq x ((x, e) : imps) more ys + else varEq x imps (def : more) ys + + +-------------------------------------------------------------------------------- +-- Higher level operations on collections of constraints. + + +-- Identify our constraints, and declare their variables in the current scope. +solverPrepare :: SolverProcess -> IORef VarInfo -> + [Ct] -> IO ([Ct], [(Ct,SExpr,Maybe TyVar)]) +solverPrepare proc viRef = go [] [] + where + go others ours [] = return (others, ours) + go others ours (ct : cts) = + case knownCt ct of + Just (vars,e,mbV) -> + do let g = if isGivenCt ct then "[G] " else "[W/D] " + solverDebug proc (g ++ show e) + mapM_ (solverDeclare proc viRef) (eltsUFM vars) + go others ((ct,e,mbV) : ours) cts + Nothing -> + go (ct : others) ours cts + + +{- +Check a list of constraints for consistency, and computer derived work. +Does not affect set off assertions in the solver. +-} +solverImprove :: SolverProcess -> IORef VarInfo + -> Bool -- ^ Should we generate given constraints? + -- If not, we generate derived ones. + -> [Ct] + -> IO ExtSolRes +solverImprove proc viRef withEv cts = + do push -- declare variables + (others,ours') <- solverPrepare proc viRef cts + let ours = [ (ct,e) | (ct,e,_) <- ours' ] + case ours of + [] -> do pop -- declarations + return (ExtSolOk []) + + (oneOfOurs,_) : _ -> + do push -- assumptions + mapM_ (assume . snd) ours + status <- check + + res <- + case status of + + -- Inconsistent: find a smaller example, then stop. + Unsat -> + do pop -- assumptions + mbRes <- solverFindConstraidction proc viRef others ours + case mbRes of + Nothing -> + fail "Bug: Failed to reporoduce contradiciton." + Just (core,rest) -> + return $ ExtSolContradiction core rest + + -- We don't know: treat as consistent. + Unknown -> do pop -- assumptions + return (ExtSolOk []) + + -- Consistent: try to compute derived work. + Sat -> + do m <- solverGetModel proc =<< readIORef viRef + + imps <- solverImproveModel proc viRef m + pop -- assumptions + + vi <- readIORef viRef + + let loc = ctLoc oneOfOurs -- XXX: Better location? + toCt (x,e) = + do tv <- varToTyVar x vi + ty <- sExprToType vi e + return $ mkNonCanonical + $ mkNewFact loc withEv (mkTyVarTy tv, ty) + return $ ExtSolOk $ mapMaybe toCt imps + + pop -- declarations + return res + where + push = solverPush proc viRef + pop = solverPop proc viRef + assume = solverAssume proc + check = solverCheck proc + + +{- Identify a sub-set of constraints that leads to a contradiction. + +We call this when we find out that a collection of constraints is +inconsistent: instead of marking them _all_ as insoluable, +we identify a sub-set that is the real cause of the problem. + +* Assumes that the variables in our constarints have been declared. +* Does not change the assertions in the solver. +-} +solverFindConstraidction :: + SolverProcess -> -- Solver + IORef VarInfo -> -- Scoping of variables + [Ct] -> -- Constraints not relevant to us + [(Ct,SExpr)] -> -- Our constraints + IO (Maybe ( [Ct] -- Constraints that cause a contradiciotn + , [Ct] -- All other constraints (both others and ours) + ) + ) +solverFindConstraidction proc viRef others ours = + do push -- scope for `needed` + minimize others [] ours + + where + check = solverCheck proc + push = solverPush proc viRef + pop = solverPop proc viRef + assume = solverAssume proc + + minimize notNeeded needed maybeNeeded = + do res <- check + case res of + Unsat -> do pop -- remove `needed` scope. + return $ Just (needed, map fst maybeNeeded ++ notNeeded) + _ -> do push -- scope for `maybeNeeded` + search notNeeded needed [] maybeNeeded + + search _ needed _ [] = + do pop -- Remove `maybeNeeded` + pop -- Remove `needed` + case needed of + [] -> return Nothing -- No definite contradictions + _ -> fail "Bug: we found a contradiction, and then lost it!" + + search notNeeded needed maybeNeeded ((ct,e) : more) = + do assume e -- Add to `maybeNeeded` + res <- check + case res of + + Unsat -> -- We found a contradiction using `needed` and `maybeNeeded`. + do pop -- remove `maybeNedded` + assume e -- add to `needed` + minimize (map fst more ++ notNeeded) (ct : needed) maybeNeeded + + -- No contradiction, keep searching. + _ -> search notNeeded needed ((ct,e) : maybeNeeded) more + + + +{- Try to solve as much as possible from the given list of constraints. +Returns the solved constraints (with evidence), and all other constraints. +-} +{- +Note [In What Order to Solve Wanteds?] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Consider this example: + +ex4 :: p a -> p b -> p ((a + a) + b) -> p (2 * a + b) +ex4 _ = id + +A: a + a = u +B: x + b = v +C: 2 * a = w +D: w + b = v + +If we solve `B` or `D` first, then we are essnetially done, +as all the others can be substituted within each other. + +However, what if we happen to consider `C` first? + +(A,B,D) => C + +This goal is essentially: + +((a + a) + b ~ (2 * a) + b) => (a + a) ~ 2 * a + +which can be proved by cancelling `b` on both sides. + +Now, we are left with just `A,B,D`, which amounts to having +to prove: + +(a + a) + b ~ w + b + +We can't do this because we've lost the information about `w`. +To avoid this, we first try to solve equations that have the same varibal +on the RHS (e.g., F xs ~ a, G ys ~ a). + +-} + + +{- +Note [Incompleteness of the General Approach] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Another tricky example, which illustrates the incompleteness of +the general method: + +x :: f (n + 2) +x = undefined + +y :: f (m + 1) +y = x + +The definition for `x` is accepted, but `y` is rejected. +The reason is that to accept it we need to infer that `m` must +be instantiated to `n + 1`. The current system does not support this +kind of improvement, because it only ever tries to instantiate variables +to constants or other variables and here we need to instantiate `m` +with a more complex expression, namely, `n + 1`. +-} + + +solverSimplify :: SolverProcess -> IORef VarInfo -> + [Ct] -> IO ([(EvTerm,Ct)], [Ct]) +solverSimplify proc viRef cts = + do push -- `unsolved` scope + (others,ours) <- solverPrepare proc viRef cts + let r = reorder ours + solverDebug proc "Reordered:" + mapM_ (solverDebug proc . show . snd) r + go others [] r + where + push = solverPush proc viRef + pop = solverPop proc viRef + assume = solverAssume proc + + go unsolved solved [] = + do pop -- remove `unsolved` scope + return (solved, unsolved) + + go unsolved solved ((ct,e) : more) = + do push -- Temporarily assume everything else. + solverDebug proc "Temporary assuming:" + solverDebugNext proc + mapM_ (assume . snd) more + mapM_ (solverDebug proc . show . snd) more + solverDebugPrev proc + proved <- solverProve proc viRef e + pop + if proved + then let ev = evBySMT "SMT" $ getEqPredTys $ ctPred ct + in go unsolved ((ev,ct) : solved) more + else do assume e -- add to `unsolved` + go (ct : unsolved) solved more + + reorder = map dropVar + . concat + . sortBy longer -- most vars first + . groupBy sameVars -- join together eqns for same variable + . sortBy cmpVars -- first sort by variable + + where + cmpVars (_,_,v1) (_,_,v2) = compare v1 v2 + sameVars (_,_,v1) (_,_,v2) = v1 == v2 + longer xs ys = compare (length ys) (length xs) + dropVar (ct,e,_) = (ct,e) + + + + + +-------------------------------------------------------------------------------- + +smtTy :: Ty -> SExpr +smtTy ty = + SAtom $ + case ty of + TNat -> "Int" + TBool -> "Bool" + +smtExtraConstraints :: String -> Ty -> [SExpr] +smtExtraConstraints x ty = + case ty of + TNat -> [ smtLeq (smtNum 0) (smtVar x) ] + TBool -> [ ] + +smtVar :: String -> SExpr +smtVar = SAtom + +smtEq :: SExpr -> SExpr -> SExpr +smtEq e1 e2 = SList [ SAtom "=", e1, e2 ] + +smtLeq :: SExpr -> SExpr -> SExpr +smtLeq e1 e2 = SList [ SAtom "<=", e1, e2 ] + +smtBool :: Bool -> SExpr +smtBool b = SAtom (if b then "true" else "false") + +smtNum :: Integer -> SExpr +smtNum x = SAtom (show x) + + + + +-------------------------------------------------------------------------------- +-- Recognizing constraints that we can work with. + + +data Ty = TNat | TBool +type VarTypes = UniqFM (TyVar,String,Ty) + + +{- | See if this constraint is one ofthe ones that we understand. +If the constraint is of the form `F ts ~ a`, where `a` is a type-variable, +we also return `a`. This is used to decide in what order to solve +constraints, see `solverSimplify`. -} +knownCt :: Ct -> Maybe (VarTypes, SExpr, Maybe TyVar) +knownCt ct = + case ct of + CFunEqCan _ f args rhs -> + do (vs1,e1) <- knownTerm f args + (vs2,e2) <- knownXi rhs + return (plusUFM vs1 vs2, smtEq e1 e2, getTyVar_maybe rhs) + _ -> Nothing + +knownTerm :: TyCon -> [Xi] -> Maybe (VarTypes, SExpr) +knownTerm tc xis = + do op <- knownTC tc + as <- mapM knownXi xis + -- XXX: Check for linearity here? + let (varMaps,es) = unzip as + return (foldl' plusUFM emptyUFM varMaps, SList (op : es)) +knownTC :: TyCon -> Maybe SExpr +knownTC tc + | tc == typeNatAddTyCon = Just $ SAtom "+" + | tc == typeNatSubTyCon = Just $ SAtom "-" + | tc == typeNatMulTyCon = Just $ SAtom "*" + | tc == typeNatLeqTyCon = Just $ SAtom "<=" + | otherwise = Nothing + +knownXi :: Xi -> Maybe (VarTypes, SExpr) +knownXi xi + | Just x <- getTyVar_maybe xi = knownVar x + | Just x <- isNumLitTy xi = Just (emptyUFM, smtNum x) + | Just x <- isBoolLitTy xi = Just (emptyUFM, smtBool x) + | otherwise = Nothing + +knownVar :: TyVar -> Maybe (VarTypes, SExpr) +knownVar x = + do t <- knownKind (tyVarKind x) + let v = thyVarName x + return (unitUFM x (x, v, t), SAtom v) + +knownKind :: Kind -> Maybe Ty +knownKind k = + case splitTyConApp_maybe k of + Just (tc,[]) + | tc == promotedBoolTyCon -> Just TBool + | tc == typeNatKindCon -> Just TNat + _ -> Nothing + +thyVarName :: TyVar -> String +thyVarName x = occNameString (nameOccName n) ++ "_" ++ show u + where n = tyVarName x + u = nameUnique n + + +-- From a value back into a type +sExprToType :: VarInfo -> SExpr -> Maybe Type +sExprToType vi expr = + case expr of + SAtom "false" -> Just (bool False) + SAtom "true" -> Just (bool True) + SAtom s + | [(n,"")] <- reads s -> Just (num n) + SAtom s + | Just v <- varToTyVar s vi -> Just (mkTyVarTy v) + _ -> Nothing + + + + +-------------------------------------------------------------------------------- + +-- Information about declared variables, so that we know how to extarct +-- models, and map them back into types. +data VarInfo = VarInfo + { smtCurScope :: Map String TyVar + , smtOtherScopes :: [Map String TyVar] + } + +emptyVarInfo :: VarInfo +emptyVarInfo = VarInfo + { smtCurScope = Map.empty + , smtOtherScopes = [] + } + +inScope :: VarInfo -> [String] +inScope vi = Map.keys $ Map.unions $ smtCurScope vi : smtOtherScopes vi + +startScope :: VarInfo -> VarInfo +startScope vi = vi { smtCurScope = Map.empty + , smtOtherScopes = smtCurScope vi : smtOtherScopes vi } + +endScope :: VarInfo -> VarInfo +endScope vi = + case smtOtherScopes vi of + [] -> panic "endScope with no start scope" + s : ss -> vi + { smtCurScope = s + , smtOtherScopes = ss + } + +varToTyVar :: String -> VarInfo -> Maybe TyVar +varToTyVar x vi = msum $ map (Map.lookup x) $ smtCurScope vi : smtOtherScopes vi + + +data VarStatus = Undeclared | Declared + +-- | Update var info, and indicate if we need to declare the variable. +declareVar :: TyVar -> VarInfo -> (VarInfo, VarStatus) +declareVar tv vi + | x `Map.member` smtCurScope vi = (vi, Declared) + | any (x `Map.member`) (smtOtherScopes vi) = (vi, Declared) + | otherwise = + ( vi { smtCurScope = Map.insert x tv (smtCurScope vi) }, Undeclared ) + where x = thyVarName tv + + + + + + +-------------------------------------------------------------------------------- +-- Low-level interaction with the solver process. + +data SExpr = SAtom String | SList [SExpr] + deriving Eq + +instance Show SExpr where + showsPrec _ = renderSExpr + +data SolverProcess = SolverProcess + { solverDo :: SExpr -> IO SExpr + , solverStop :: IO () + + -- For debguggning + , solverDebug :: String -> IO () + , solverDebugNext :: IO () + , solverDebugPrev :: IO () + } + +startSolverProcess :: String -> [String] -> IO SolverProcess +startSolverProcess exe opts = + do (hIn, hOut, hErr, h) <- runInteractiveProcess exe opts Nothing Nothing + + dbgNest <- newIORef (0 :: Int) + let dbgMsg x = do n <- readIORef dbgNest + hPutStrLn stderr (replicate n ' ' ++ x) + dbgNext = modifyIORef' dbgNest (+2) + dbgPrev = modifyIORef' dbgNest (subtract 2) + + -- XXX: Ignore errors for now. + _ <- forkIO $ do forever (putStrLn =<< hGetLine hErr) + `X.catch` \X.SomeException {} -> return () + + -- XXX: No real error-handling here. + getResponse <- + do txt <- hGetContents hOut + ref <- newIORef (unfoldr parseSExpr txt) + return $ + atomicModifyIORef ref $ \xs -> + case xs of + [] -> (xs, Nothing) + y : ys -> (ys, Just y) + + let cmd' c = do let e = renderSExpr c "" + -- dbgMsg ("[->] " ++ e) + hPutStrLn hIn e + hFlush hIn + + return SolverProcess + { solverDo = \c -> do cmd' c + mb <- getResponse + case mb of + Just res -> + do -- dbgMsg ("[<-] " ++ renderSExpr res "") + return res + Nothing -> fail "Missing response from solver" + , solverStop = + do cmd' (SList [SAtom "exit"]) + _exitCode <- waitForProcess h + return () + + , solverDebug = dbgMsg + , solverDebugNext = dbgNext + , solverDebugPrev = dbgPrev + } + +renderSExpr :: SExpr -> ShowS +renderSExpr ex = + case ex of + SAtom x -> showString x + SList es -> showChar '(' . + foldr (\e m -> renderSExpr e . showChar ' ' . m) + (showChar ')') es + +parseSExpr :: String -> Maybe (SExpr, String) +parseSExpr (c : more) | isSpace c = parseSExpr more +parseSExpr ('(' : more) = do (xs,more1) <- list more + return (SList xs, more1) + where + list (c : txt) | isSpace c = list txt + list (')' : txt) = return ([], txt) + list txt = do (v,txt1) <- parseSExpr txt + (vs,txt2) <- list txt1 + return (v:vs, txt2) +parseSExpr txt = case break end txt of + (as,bs) | not (null as) -> Just (SAtom as, bs) + _ -> Nothing + where end x = x == ')' || isSpace x |