{- Author: George Karachalias The term equality oracle. The main export of the module is function `tmOracle'. -} {-# LANGUAGE CPP, MultiWayIf #-} module TmOracle ( -- re-exported from PmExpr PmExpr(..), PmLit(..), SimpleEq, ComplexEq, PmVarEnv, falsePmExpr, eqPmLit, filterComplex, isNotPmExprOther, runPmPprM, lhsExprToPmExpr, hsExprToPmExpr, pprPmExprWithParens, -- the term oracle tmOracle, TmState, initialTmState, solveOneEq, extendSubst, canDiverge, -- misc. toComplex, exprDeepLookup, pmLitType, flattenPmVarEnv ) where #include "HsVersions.h" import GhcPrelude import PmExpr import Id import Name import Type import HsLit import TcHsSyn import MonadUtils import Util import NameEnv {- %************************************************************************ %* * The term equality oracle %* * %************************************************************************ -} -- | The type of substitutions. type PmVarEnv = NameEnv PmExpr -- | The environment of the oracle contains -- 1. A Bool (are there any constraints we cannot handle? (PmExprOther)). -- 2. A substitution we extend with every step and return as a result. type TmOracleEnv = (Bool, PmVarEnv) -- | Check whether a constraint (x ~ BOT) can succeed, -- given the resulting state of the term oracle. canDiverge :: Name -> TmState -> Bool canDiverge x (standby, (_unhandled, env)) -- If the variable seems not evaluated, there is a possibility for -- constraint x ~ BOT to be satisfiable. | PmExprVar y <- varDeepLookup env x -- seems not forced -- If it is involved (directly or indirectly) in any equality in the -- worklist, we can assume that it is already indirectly evaluated, -- as a side-effect of equality checking. If not, then we can assume -- that the constraint is satisfiable. = not $ any (isForcedByEq x) standby || any (isForcedByEq y) standby -- Variable x is already in WHNF so the constraint is non-satisfiable | otherwise = False where isForcedByEq :: Name -> ComplexEq -> Bool isForcedByEq y (e1, e2) = varIn y e1 || varIn y e2 -- | Check whether a variable is in the free variables of an expression varIn :: Name -> PmExpr -> Bool varIn x e = case e of PmExprVar y -> x == y PmExprCon _ es -> any (x `varIn`) es PmExprLit _ -> False PmExprEq e1 e2 -> (x `varIn` e1) || (x `varIn` e2) PmExprOther _ -> False -- | Flatten the DAG (Could be improved in terms of performance.). flattenPmVarEnv :: PmVarEnv -> PmVarEnv flattenPmVarEnv env = mapNameEnv (exprDeepLookup env) env -- | The state of the term oracle (includes complex constraints that cannot -- progress unless we get more information). type TmState = ([ComplexEq], TmOracleEnv) -- | Initial state of the oracle. initialTmState :: TmState initialTmState = ([], (False, emptyNameEnv)) -- | Solve a complex equality (top-level). solveOneEq :: TmState -> ComplexEq -> Maybe TmState solveOneEq solver_env@(_,(_,env)) complex = solveComplexEq solver_env -- do the actual *merging* with existing state $ simplifyComplexEq -- simplify as much as you can $ applySubstComplexEq env complex -- replace everything we already know -- | Solve a complex equality. solveComplexEq :: TmState -> ComplexEq -> Maybe TmState solveComplexEq solver_state@(standby, (unhandled, env)) eq@(e1, e2) = case eq of -- We cannot do a thing about these cases (PmExprOther _,_) -> Just (standby, (True, env)) (_,PmExprOther _) -> Just (standby, (True, env)) (PmExprLit l1, PmExprLit l2) -> case eqPmLit l1 l2 of -- See Note [Undecidable Equality for Overloaded Literals] True -> Just solver_state False -> Nothing (PmExprCon c1 ts1, PmExprCon c2 ts2) | c1 == c2 -> foldlM solveComplexEq solver_state (zip ts1 ts2) | otherwise -> Nothing (PmExprCon _ [], PmExprEq t1 t2) | isTruePmExpr e1 -> solveComplexEq solver_state (t1, t2) | isFalsePmExpr e1 -> Just (eq:standby, (unhandled, env)) (PmExprEq t1 t2, PmExprCon _ []) | isTruePmExpr e2 -> solveComplexEq solver_state (t1, t2) | isFalsePmExpr e2 -> Just (eq:standby, (unhandled, env)) (PmExprVar x, PmExprVar y) | x == y -> Just solver_state | otherwise -> extendSubstAndSolve x e2 solver_state (PmExprVar x, _) -> extendSubstAndSolve x e2 solver_state (_, PmExprVar x) -> extendSubstAndSolve x e1 solver_state (PmExprEq _ _, PmExprEq _ _) -> Just (eq:standby, (unhandled, env)) _ -> Just (standby, (True, env)) -- I HATE CATCH-ALLS -- | Extend the substitution and solve the (possibly updated) constraints. extendSubstAndSolve :: Name -> PmExpr -> TmState -> Maybe TmState extendSubstAndSolve x e (standby, (unhandled, env)) = foldlM solveComplexEq new_incr_state (map simplifyComplexEq changed) where -- Apply the substitution to the worklist and partition them to the ones -- that had some progress and the rest. Then, recurse over the ones that -- had some progress. Careful about performance: -- See Note [Representation of Term Equalities] in deSugar/Check.hs (changed, unchanged) = partitionWith (substComplexEq x e) standby new_incr_state = (unchanged, (unhandled, extendNameEnv env x e)) -- | When we know that a variable is fresh, we do not actually have to -- check whether anything changes, we know that nothing does. Hence, -- `extendSubst` simply extends the substitution, unlike what -- `extendSubstAndSolve` does. extendSubst :: Id -> PmExpr -> TmState -> TmState extendSubst y e (standby, (unhandled, env)) | isNotPmExprOther simpl_e = (standby, (unhandled, extendNameEnv env x simpl_e)) | otherwise = (standby, (True, env)) where x = idName y simpl_e = fst $ simplifyPmExpr $ exprDeepLookup env e -- | Simplify a complex equality. simplifyComplexEq :: ComplexEq -> ComplexEq simplifyComplexEq (e1, e2) = (fst $ simplifyPmExpr e1, fst $ simplifyPmExpr e2) -- | Simplify an expression. The boolean indicates if there has been any -- simplification or if the operation was a no-op. simplifyPmExpr :: PmExpr -> (PmExpr, Bool) -- See Note [Deep equalities] simplifyPmExpr e = case e of PmExprCon c ts -> case mapAndUnzip simplifyPmExpr ts of (ts', bs) -> (PmExprCon c ts', or bs) PmExprEq t1 t2 -> simplifyEqExpr t1 t2 _other_expr -> (e, False) -- the others are terminals -- | Simplify an equality expression. The equality is given in parts. simplifyEqExpr :: PmExpr -> PmExpr -> (PmExpr, Bool) -- See Note [Deep equalities] simplifyEqExpr e1 e2 = case (e1, e2) of -- Varables (PmExprVar x, PmExprVar y) | x == y -> (truePmExpr, True) -- Literals (PmExprLit l1, PmExprLit l2) -> case eqPmLit l1 l2 of -- See Note [Undecidable Equality for Overloaded Literals] True -> (truePmExpr, True) False -> (falsePmExpr, True) -- Can potentially be simplified (PmExprEq {}, _) -> case (simplifyPmExpr e1, simplifyPmExpr e2) of ((e1', True ), (e2', _ )) -> simplifyEqExpr e1' e2' ((e1', _ ), (e2', True )) -> simplifyEqExpr e1' e2' ((e1', False), (e2', False)) -> (PmExprEq e1' e2', False) -- cannot progress (_, PmExprEq {}) -> case (simplifyPmExpr e1, simplifyPmExpr e2) of ((e1', True ), (e2', _ )) -> simplifyEqExpr e1' e2' ((e1', _ ), (e2', True )) -> simplifyEqExpr e1' e2' ((e1', False), (e2', False)) -> (PmExprEq e1' e2', False) -- cannot progress -- Constructors (PmExprCon c1 ts1, PmExprCon c2 ts2) | c1 == c2 -> let (ts1', bs1) = mapAndUnzip simplifyPmExpr ts1 (ts2', bs2) = mapAndUnzip simplifyPmExpr ts2 (tss, _bss) = zipWithAndUnzip simplifyEqExpr ts1' ts2' worst_case = PmExprEq (PmExprCon c1 ts1') (PmExprCon c2 ts2') in if | not (or bs1 || or bs2) -> (worst_case, False) -- no progress | all isTruePmExpr tss -> (truePmExpr, True) | any isFalsePmExpr tss -> (falsePmExpr, True) | otherwise -> (worst_case, False) | otherwise -> (falsePmExpr, True) -- We cannot do anything about the rest.. _other_equality -> (original, False) where original = PmExprEq e1 e2 -- reconstruct equality -- | Apply an (un-flattened) substitution to a simple equality. applySubstComplexEq :: PmVarEnv -> ComplexEq -> ComplexEq applySubstComplexEq env (e1,e2) = (exprDeepLookup env e1, exprDeepLookup env e2) -- | Apply an (un-flattened) substitution to a variable. varDeepLookup :: PmVarEnv -> Name -> PmExpr varDeepLookup env x | Just e <- lookupNameEnv env x = exprDeepLookup env e -- go deeper | otherwise = PmExprVar x -- terminal {-# INLINE varDeepLookup #-} -- | Apply an (un-flattened) substitution to an expression. exprDeepLookup :: PmVarEnv -> PmExpr -> PmExpr exprDeepLookup env (PmExprVar x) = varDeepLookup env x exprDeepLookup env (PmExprCon c es) = PmExprCon c (map (exprDeepLookup env) es) exprDeepLookup env (PmExprEq e1 e2) = PmExprEq (exprDeepLookup env e1) (exprDeepLookup env e2) exprDeepLookup _ other_expr = other_expr -- PmExprLit, PmExprOther -- | External interface to the term oracle. tmOracle :: TmState -> [ComplexEq] -> Maybe TmState tmOracle tm_state eqs = foldlM solveOneEq tm_state eqs -- | Type of a PmLit pmLitType :: PmLit -> Type -- should be in PmExpr but gives cyclic imports :( pmLitType (PmSLit lit) = hsLitType lit pmLitType (PmOLit _ lit) = overLitType lit {- Note [Deep equalities] ~~~~~~~~~~~~~~~~~~~~~~~~~ Solving nested equalities is the most difficult part. The general strategy is the following: * Equalities of the form (True ~ (e1 ~ e2)) are transformed to just (e1 ~ e2) and then treated recursively. * Equalities of the form (False ~ (e1 ~ e2)) cannot be analyzed unless we know more about the inner equality (e1 ~ e2). That's exactly what `simplifyEqExpr' tries to do: It takes e1 and e2 and either returns truePmExpr, falsePmExpr or (e1' ~ e2') in case it is uncertain. Note that it is not e but rather e', since it may perform some simplifications deeper. -}