{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE ExistentialQuantification #-} -------------------------------------------------------------------------------- -- | Axiom Instantiation ------------------------------------------------------ -------------------------------------------------------------------------------- module Language.Fixpoint.Solver.Instantiate ( instantiate ) where import Language.Fixpoint.Types import Language.Fixpoint.Types.Config as FC import qualified Language.Fixpoint.Types.Visitor as Vis import qualified Language.Fixpoint.Misc as Misc -- (mapFst) import Language.Fixpoint.Misc ((<<=)) import qualified Language.Fixpoint.Smt.Interface as SMT import Language.Fixpoint.Defunctionalize import Language.Fixpoint.SortCheck import Language.Fixpoint.Solver.Sanitize (symbolEnv) import Control.Monad.State -- AT: I've inlined this, but we should have a more elegant solution -- (track predicates instead of selectors!) -- import Language.Haskell.Liquid.GHC.Misc (dropModuleNames) import qualified Data.Text as T import qualified Data.HashMap.Strict as M import qualified Data.List as L import Data.Maybe (isNothing, catMaybes, fromMaybe) import Data.Char (isUpper) -- import Text.Printf (printf) (~>) :: (Expr, String) -> Expr -> EvalST Expr (_e,_str) ~> e' = do modify (\st -> st {evId = evId st + 1}) return (wtf e') -------------------------------------------------------------------------------- -- | Instantiate Axioms -------------------------------------------------------------------------------- instantiate :: Config -> SInfo a -> IO (SInfo a) instantiate cfg fi | rewriteAxioms cfg = instantiate' cfg fi | otherwise = return fi instantiate' :: Config -> GInfo SimpC a -> IO (SInfo a) instantiate' cfg fi = sInfo cfg fi env <$> withCtx cfg file env act where act ctx = forM cstrs $ \(i, c) -> (i,) . notracepp ("INSTANTIATE i = " ++ show i) <$> instSimpC cfg ctx (bs fi) aenv i c cstrs = M.toList (cm fi) file = srcFile cfg ++ ".evals" env = symbolEnv cfg fi aenv = {- tracepp "AXIOM-ENV" -} (ae fi) sInfo :: Config -> GInfo SimpC a -> SymEnv -> [(SubcId, Expr)] -> SInfo a sInfo cfg fi env ips = strengthenHyp fi' (notracepp "ELAB-INST: " $ zip is ps'') where (is, ps) = unzip ips (ps', axs) = defuncAxioms cfg env ps ps'' = elaborate "PLE1" env <$> ps' axs' = elaborate "PLE2" env <$> axs fi' = fi { asserts = axs' ++ asserts fi } withCtx :: Config -> FilePath -> SymEnv -> (SMT.Context -> IO a) -> IO a withCtx cfg file env k = do ctx <- SMT.makeContextWithSEnv cfg file env _ <- SMT.smtPush ctx res <- k ctx _ <- SMT.cleanupContext ctx return res instSimpC :: Config -> SMT.Context -> BindEnv -> AxiomEnv -> Integer -> SimpC a -> IO Expr instSimpC _ _ _ aenv sid _ | not (M.lookupDefault False sid (aenvExpand aenv)) = return PTrue instSimpC cfg ctx bds aenv _ sub = -- tracepp ("instSimpC " ++ show sid) . pAnd . (is0 ++) <$> if rewriteAxioms cfg then evalEqs else return [] where is0 = eqBody <$> L.filter (null . eqArgs) eqs evalEqs = map (uncurry (PAtom Eq)) . filter (uncurry (/=)) <$> evaluate cfg ctx binds aenv iExprs eqs = aenvEqs aenv (binds, iExprs) = cstrBindExprs bds sub cstrBindExprs :: BindEnv -> SimpC a -> ([(Symbol, SortedReft)], [Expr]) cstrBindExprs bds sub = {- tracepp "initExpressions" -} (unElab <$> binds, unElab <$> es) where es = {- expr (slhs sub) : -} (crhs sub) : (expr <$> binds) binds = envCs bds (senv sub) -- _tx e = tracepp ("UNELAB e = " ++ showpp e) (unElab e) unElab :: (Vis.Visitable t) => t -> t unElab = Vis.stripCasts . unApply unApply :: (Vis.Visitable t) => t -> t unApply = Vis.trans (Vis.defaultVisitor { Vis.txExpr = const go }) () () where go (ECst (EApp (EApp f e1) e2) _) | Just _ <- unApplyAt f = EApp e1 e2 go e = e -------------------------------------------------------------------------------- -- | Knowledge (SMT Interaction) -------------------------------------------------------------------------------- data Knowledge = KN { knSims :: ![Rewrite] , knAms :: ![Equation] , knContext :: IO SMT.Context , knPreds :: !([(Symbol, Sort)] -> Expr -> SMT.Context -> IO Bool) , knLams :: [(Symbol, Sort)] } emptyKnowledge :: IO SMT.Context -> Knowledge emptyKnowledge ctx = KN [] [] ctx (\_ _ _ -> return False) [] isValid :: Knowledge -> Expr -> IO Bool isValid γ b = knPreds γ (knLams γ) b =<< knContext γ makeKnowledge :: Config -> SMT.Context -> AxiomEnv -> [(Symbol, SortedReft)] -> ([(Expr, Expr)], Knowledge) makeKnowledge cfg ctx aenv es = (simpleEqs,) $ (emptyKnowledge context) { knSims = aenvSimpl aenv , knAms = aenvEqs aenv , knPreds = \bs e c -> askSMT c bs e } where senv = SMT.ctxSymEnv ctx context :: IO SMT.Context context = do SMT.smtPop ctx SMT.smtPush ctx -- SMT.smtDecls ctx $ L.nub [(x, toSMT [] s) | (x, s) <- fbinds, not (memberSEnv x thySyms)] SMT.smtAssert ctx (pAnd ([toSMT [] (PAtom Eq e1 e2) | (e1, e2) <- simpleEqs] ++ filter (null . Vis.kvars) ((toSMT [] . expr) <$> es) )) return ctx -- This creates the rewrite rule e1 -> e2. When should I apply it? -- 1. when e2 is a data con and can lead to further reductions -- 2. when size e2 < size e1 simpleEqs = {- tracepp "SIMPLEEQS" $ -} makeSimplifications (aenvSimpl aenv) =<< L.nub (catMaybes [_getDCEquality senv e1 e2 | PAtom Eq e1 e2 <- atms]) atms = splitPAnd =<< (expr <$> filter isProof es) isProof (_, RR s _) = showpp s == "Tuple" toSMT bs = defuncAny cfg senv . elaborate "makeKnowledge" (elabEnv bs) elabEnv = L.foldl' (\env (x, s) -> insertSymEnv x s env) senv -- AT: Non-obvious needed invariant: askSMT True is always the -- totality-effecting one askSMT :: SMT.Context -> [(Symbol, Sort)] -> Expr -> IO Bool askSMT ctx bs e | isTautoPred e = return True -- // Why? | isContraPred e = return False -- Why the f? | null (Vis.kvars e) = do SMT.smtPush ctx b <- SMT.checkValid' ctx [] PTrue (toSMT bs e) SMT.smtPop ctx return b | otherwise = return False makeSimplifications :: [Rewrite] -> (Symbol, [Expr], Expr) -> [(Expr, Expr)] makeSimplifications sis (dc, es, e) = go =<< sis where go (SMeasure f dc' xs bd) | dc == dc', length xs == length es = [(EApp (EVar f) e, subst (mkSubst $ zip xs es) bd)] go _ = [] _getDCEquality :: SymEnv -> Expr -> Expr -> Maybe (Symbol, [Expr], Expr) _getDCEquality senv e1 e2 | Just dc1 <- f1 , Just dc2 <- f2 = if dc1 == dc2 then Nothing else error ("isDCEquality on" ++ showpp e1 ++ "\n" ++ showpp e2) | Just dc1 <- f1 = Just (dc1, es1, e2) | Just dc2 <- f2 = Just (dc2, es2, e1) | otherwise = Nothing where (f1, es1) = Misc.mapFst (getDC senv) (splitEApp e1) (f2, es2) = Misc.mapFst (getDC senv) (splitEApp e2) -- TODO: Stringy hacks getDC :: SymEnv -> Expr -> Maybe Symbol getDC senv (EVar x) | isUpperSymbol x && isNothing (symEnvTheory x senv) = Just x getDC _ _ = Nothing isUpperSymbol :: Symbol -> Bool isUpperSymbol = isUpper . headSym . dropModuleNames dropModuleNames :: Symbol -> Symbol dropModuleNames = mungeNames (symbol . last) "." where mungeNames _ _ "" = "" mungeNames f d s'@(symbolText -> s) | s' == tupConName = tupConName | otherwise = f $ T.splitOn d $ stripParens s stripParens t = fromMaybe t ((T.stripPrefix "(" >=> T.stripSuffix ")") t) splitPAnd :: Expr -> [Expr] splitPAnd (PAnd es) = concatMap splitPAnd es splitPAnd e = [e] -------------------------------------------------------------------------------- -- | Creating Measure Info -------------------------------------------------------------------------------- -- AT@TODO do this for all reflected functions, not just DataCons {- [NOTE:Datacon-Selectors] The 'assertSelectors' function insert measure information for every constructor that appears in the expression e. In theory, this is not required as the SMT ADT encoding takes care of it. However, in practice, some constructors, e.g. from GADTs cannot be directly encoded in SMT due to the lack of SMTLIB support for GADT. Hence, we still need to hang onto this code. See tests/proof/ple2.fq for a concrete example. -} assertSelectors :: Knowledge -> Expr -> EvalST () -- assertSelectors _ _ = return () {- TODO: HEREHEREHEREHEREHEREHERE 1. DOES this kill Unification.hs? (Guard under --no-adt) 2. Use addEquality instead off _addSMTEquality. -} assertSelectors γ e = do sims <- aenvSimpl <$> gets _evAEnv -- cfg <- gets evCfg -- _ <- foldlM (\_ s -> Vis.mapMExpr (go s) e) (tracepp "assertSelector" e) sims forM_ sims $ \s -> Vis.mapMExpr (go s) e return () where go :: Rewrite -> Expr -> EvalST Expr go (SMeasure f dc xs bd) e@(EApp _ _) | (EVar dc', es) <- splitEApp e , dc == dc' , length xs == length es = do let e1 = (EApp (EVar f) e) let e2 = (subst (mkSubst $ zip xs es) bd) addEquality γ e1 e2 return e go _ e = return e -- _addSMTEquality :: Knowledge -> Expr -> Expr -> IO () -- _addSMTEquality γ e1 e2 = do -- ctx <- knContext γ -- SMT.smtAssert ctx (tracepp "addSMTEQ" (PAtom Eq (makeLam γ e1) (makeLam γ e2))) -------------------------------------------------------------------------------- -- | Symbolic Evaluation with SMT -------------------------------------------------------------------------------- data EvalEnv = EvalEnv { evId :: !Int , evSequence :: [(Expr,Expr)] , _evAEnv :: !AxiomEnv , evEnv :: !SymEnv , _evCfg :: !Config } type EvalST a = StateT EvalEnv IO a evaluate :: Config -> SMT.Context -> [(Symbol, SortedReft)] -> AxiomEnv -> [Expr] -> IO [(Expr, Expr)] evaluate cfg ctx facts aenv einit = (eqs ++) <$> (fmap join . sequence) (evalOne <$> L.nub (grepTopApps =<< einit)) where (eqs, γ) = makeKnowledge cfg ctx aenv facts senv = SMT.ctxSymEnv ctx initEvalSt = EvalEnv 0 [] aenv senv cfg -- This adds all intermediate unfoldings into the assumptions -- no test needs it -- TODO: add a flag to enable it evalOne :: Expr -> IO [(Expr, Expr)] evalOne e = {- notracepp ("evalOne e = " ++ showpp e) <$> -} do (e', st) <- runStateT (eval γ e) initEvalSt if e' == e then return [] else return ((e, e'):evSequence st) -- Don't evaluate under Lam, App, Ite, or constants grepTopApps :: Expr -> [Expr] grepTopApps (PAnd es) = concatMap grepTopApps es grepTopApps (POr es) = concatMap grepTopApps es grepTopApps (PAtom _ e1 e2) = grepTopApps e1 ++ grepTopApps e2 grepTopApps (PIff e1 e2) = grepTopApps e1 ++ grepTopApps e2 grepTopApps (PImp e1 e2) = grepTopApps e1 ++ grepTopApps e2 grepTopApps (PNot e) = grepTopApps e grepTopApps (EBin _ e1 e2) = grepTopApps e1 ++ grepTopApps e2 grepTopApps (ENeg e) = grepTopApps e grepTopApps e@(EApp _ _) = [e] grepTopApps _ = [] -- makeLam is the adjoint of splitEApp makeLam :: Knowledge -> Expr -> Expr makeLam γ e = L.foldl' (flip ELam) e (knLams γ) eval :: Knowledge -> Expr -> EvalST Expr eval γ (ELam (x,s) e) = do e' <- eval γ{knLams = (x, s) : knLams γ} e return $ ELam (x, s) e' eval γ e@(EIte b e1 e2) = do b' <- eval γ b evalIte γ e b' e1 e2 eval γ (ECoerc s t e) = ECoerc s t <$> eval γ e eval γ e@(EApp _ _) = evalArgs γ e >>= evalApp γ e eval γ e@(EVar _) = evalApp γ e (e,[]) eval γ (PAtom r e1 e2) = PAtom r <$> eval γ e1 <*> eval γ e2 eval γ (ENeg e) = ENeg <$> eval γ e eval γ (EBin o e1 e2) = EBin o <$> eval γ e1 <*> eval γ e2 eval γ (ETApp e t) = flip ETApp t <$> eval γ e eval γ (ETAbs e s) = flip ETAbs s <$> eval γ e eval γ (PNot e) = PNot <$> eval γ e eval γ (PImp e1 e2) = PImp <$> eval γ e1 <*> eval γ e2 eval γ (PIff e1 e2) = PIff <$> eval γ e1 <*> eval γ e2 eval γ (PAnd es) = PAnd <$> (eval γ <$$> es) eval γ (POr es) = POr <$> (eval γ <$$> es) eval _ e = return e (<$$>) :: (Monad m) => (a -> m b) -> [a] -> m [b] f <$$> xs = f Misc.<$$> xs evalArgs :: Knowledge -> Expr -> EvalST (Expr, [Expr]) evalArgs γ = go [] where go acc (EApp f e) = do f' <- eval γ f e' <- eval γ e go (e':acc) f' go acc e = (,acc) <$> eval γ e evalApp :: Knowledge -> Expr -> (Expr, [Expr]) -> EvalST Expr evalApp γ e (EVar f, [ex]) | (EVar dc, es) <- splitEApp ex , Just simp <- L.find (\simp -> (smName simp == f) && (smDC simp == dc)) (knSims γ) , length (smArgs simp) == length es = do e' <- eval γ $ wtf $ substPopIf (zip (smArgs simp) es) (smBody simp) (e, "Rewrite -" ++ showpp f) ~> e' evalApp γ _ (EVar f, es) -- we should move the lookupKnowledge stuff here into kmAms γ | Just eq <- L.find ((==f) . eqName) (knAms γ) , Just bd <- getEqBody eq , length (eqArgs eq) == length es , f `notElem` syms bd -- non-recursive equations = eval γ . wtf =<< assertSelectors γ <<= substEq PopIf eq es bd evalApp γ _e (EVar f, es) | Just eq <- L.find ((==f) . eqName) (knAms γ) , Just bd <- getEqBody eq , length (eqArgs eq) == length es -- recursive equations = evalRecApplication γ (eApps (EVar f) es) =<< substEq Normal eq es bd evalApp _ _ (f, es) = return $ eApps f es -------------------------------------------------------------------------------- -- | 'substEq' unfolds or instantiates an equation at a particular list of -- argument values. We must also substitute the sort-variables that appear -- as coercions. See tests/proof/ple1.fq -------------------------------------------------------------------------------- substEq :: SubstOp -> Equation -> [Expr] -> Expr -> EvalST Expr substEq o eq es bd = substEqVal o eq es <$> substEqCoerce eq es bd data SubstOp = PopIf | Normal substEqVal :: SubstOp -> Equation -> [Expr] -> Expr -> Expr substEqVal o eq es bd = case o of PopIf -> substPopIf xes bd Normal -> subst (mkSubst xes) bd where xes = zip xs es xs = eqArgNames eq substEqCoerce :: Equation -> [Expr] -> Expr -> EvalST Expr substEqCoerce eq es bd = do env <- seSort <$> gets evEnv let ts = snd <$> eqArgs eq let coSub = mkCoSub env es ts return $ applyCoSub coSub bd -- | @CoSub@ is a map from (coercion) ty-vars represented as 'FObj s' -- to the ty-vars that they should be substituted with. Note the -- domain and range are both Symbol and not the Int used for real ty-vars. type CoSub = M.HashMap Symbol Symbol mkCoSub :: SEnv Sort -> [Expr] -> [Sort] -> CoSub mkCoSub env es xTs = Misc.safeFromList "mkCoSub" xys where eTs = sortExpr sp env <$> es sp = panicSpan "mkCoSub" xys = concat (zipWith matchSorts xTs eTs) matchSorts :: Sort -> Sort -> [(Symbol, Symbol)] matchSorts = go where go (FObj x) (FObj y) = [(x, y)] go (FAbs _ t1) (FAbs _ t2) = go t1 t2 go (FFunc s1 t1) (FFunc s2 t2) = go s1 s2 ++ go t1 t2 go (FApp s1 t1) (FApp s2 t2) = go s1 s2 ++ go t1 t2 go _ _ = [] applyCoSub :: CoSub -> Expr -> Expr applyCoSub coSub = Vis.mapExpr fE where fE (ECoerc s t e) = ECoerc (txS s) (txS t) e fE e = e txS = Vis.mapSort fS fS (FObj a) = FObj (txV a) fS t = t txV a = M.lookupDefault a a coSub -------------------------------------------------------------------------------- getEqBody :: Equation -> Maybe Expr getEqBody (Equ x xts b _ _) | Just (fxs, e) <- getEqBodyPred b , (EVar f, es) <- splitEApp fxs , f == x , es == (EVar . fst <$> xts) = Just e getEqBody _ = Nothing getEqBodyPred :: Expr -> Maybe (Expr, Expr) getEqBodyPred (PAtom Eq fxs e) = Just (fxs, e) getEqBodyPred (PAnd ((PAtom Eq fxs e):_)) = Just (fxs, e) getEqBodyPred _ = Nothing eqArgNames :: Equation -> [Symbol] eqArgNames = map fst . eqArgs substPopIf :: [(Symbol, Expr)] -> Expr -> Expr substPopIf xes e = wtf $ L.foldl' go e xes where go e (x, EIte b e1 e2) = EIte b (subst1 e (x, e1)) (subst1 e (x, e2)) go e (x, ex) = subst1 e (x, ex) evalRecApplication :: Knowledge -> Expr -> Expr -> EvalST Expr evalRecApplication γ e (EIte b e1 e2) = do contra <- {- tracepp ("CONTRA? " ++ showpp e) <$> -} liftIO (isValid γ PFalse) if contra then return e else do b' <- eval γ b b1 <- liftIO (isValid γ b') if b1 then addEquality γ e e1 >> ({-# SCC "assertSelectors-1" #-} assertSelectors γ e1) >> eval γ e1 >>= ((e, "App") ~>) else do b2 <- liftIO (isValid γ (PNot b')) if b2 then addEquality γ e e2 >> ({-# SCC "assertSelectors-2" #-} assertSelectors γ e2) >> eval γ e2 >>= ((e, "App") ~>) else return e evalRecApplication _ _ e = return e addEquality :: Knowledge -> Expr -> Expr -> EvalST () addEquality γ e1 e2 = modify (\st -> st{evSequence = (makeLam γ e1, makeLam γ e2):evSequence st}) evalIte :: Knowledge -> Expr -> Expr -> Expr -> Expr -> EvalST Expr evalIte γ e b e1 e2 = join $ evalIte' γ e b e1 e2 <$> liftIO (isValid γ b) <*> liftIO (isValid γ (PNot b)) evalIte' :: Knowledge -> Expr -> Expr -> Expr -> Expr -> Bool -> Bool -> EvalST Expr evalIte' γ e _ e1 _ b _ | b = do e' <- eval γ e1 (e, "If-True of:" ++ showpp b) ~> e' evalIte' γ e _ _ e2 _ b' | b' = do e' <- eval γ e2 (e, "If-False") ~> e' evalIte' γ _ b e1 e2 _ _ = do e1' <- eval γ e1 e2' <- eval γ e2 return $ EIte b e1' e2' -------------------------------------------------------------------------------- -- normalization required by ApplicativeMaybe.composition -------------------------------------------------------------------------------- -- RJ: What on earth is this function doing? wtf :: Expr -> Expr wtf = id {- wtf = snd . go where go (EIte b t f) | isTautoPred t && isFalse f = (True, b) go (EIte b e1 e2) = let (fb, b') = go b (f1, e1') = go e1 (f2, e2') = go e2 in (fb || f1 || f2, EIte b' e1' e2') go (EApp (EIte b f1 f2) e) = (True, EIte b (snd $ go $ EApp f1 e) (snd $ go $ EApp f2 e)) go (EApp f (EIte b e1 e2)) = (True, EIte b (snd $ go $ EApp f e1) (snd $ go $ EApp f e2)) go (EApp e1 e2) = let (f1, e1') = go e1 (f2, e2') = go e2 in if f1 || f2 then go $ EApp e1' e2' else (False, EApp e1' e2') go e = (False, e) -} instance Expression (Symbol, SortedReft) where expr (x, RR _ (Reft (v, r))) = subst1 (expr r) (v, EVar x)