--- ---------------------------------------------------------------------------- --- This module performs the abstraction of expressions. --- Depending on the criteria used for abstractions, this module comes to a --- decision if parts of the given expressions should be further evaluated. --- This part is crucial to ensure termination of the whole process. --- --- *Note:* In contrast to the original work we do not remove expressions --- from the set during abstraction because we want to avoid both --- re-evaluation of expressions or the loss of the more specific results. --- --- @author Elvira Albert, German Vidal, Michael Hanus, Björn Peemöller --- @version September 2015 --- ---------------------------------------------------------------------------- module Abstract (abstract) where import AnsiCodes (yellow) import Function (on, second) import List (delete, find, maximumBy, sum) import Maybe (isJust) import Pretty (Doc, (<+>), ($$), (<>), equals, pPrint, text, vsep) import Utils (none, sameLength) import FlatCurry.Types import FlatCurryGoodies ( branchExprs, completePartCall, isVar, isConsCall , onBranchExps, prelApply, prelude, samePattern, sq , sq', subExprs, isFailed, funcName, getSQ) import FlatCurryPretty (ppExp, indent) import Instance (instance, instanceOf, msg) import Normalization (eqNorm, normalizeExpr) import Output (assert, colorWith, debug, traceDetail) import PevalOpts (Options (optAbstract, optAssert), Abstraction (..)) import Subst (ppSubst, rng) --- The sequence of expressions to be further evaluated. --- We use a sequence instead of a set to be able to use the anti-transitivity --- of the well-founded ordering. type AbsSet = [Expr] --- Abstraction operator. --- @param otps - Options passed to partiall evaluator. --- @param p - program. --- @param q - already abstracted expressions. --- @param es - new expressions to be abstracted. abstract :: Options -> Prog -> AbsSet -> [Expr] -> AbsSet abstract opts (Prog _ _ _ fs _) q es | optAssert opts = originProp $ closednessProp $ orderingProp q' | otherwise = q' where q' = absAll opts q es qes = q ++ es -- origin property: abstraction does not "invent" new expressions originProp q0 = assert (originateFrom q0 qes) ("Abstraction: origin property violated\n" ++ pPrint (vsep (map ppExp q0)) ++ "\ndoes not originate from\n" ++ pPrint (vsep (map ppExp qes))) q0 -- closedness property: abstraction subsumes all expressions closednessProp q0 = assert (null notClosed) ("Abstraction: closedness property violated\n" ++ pPrint (vsep (map ppExp notClosed)) ++ "\nis/are not closed with respect to\n" ++ pPrint (vsep (map ppExp q0))) q0 where notClosed = filter (not . closed (map funcName fs) q0) qes -- ordering property: abstraction fulfills the property of the used ordering orderingProp q0 = case optAbstract opts of None -> q0 WFO -> assert (decreasing q0) "Abstraction: decreasing size property violated" q0 WQO -> assert (nonembedding q0) "Abstraction: nonembedding property violated" q0 --- Trace an abstraction action. traceAbs :: Options -> Doc -> a -> a traceAbs opts doc x = traceDetail opts (colorWith opts yellow str) x where str = pPrint (text "Abstraction:" <+> doc) ++ "\n" --- Trace that the addition of an expression to the set is absorbed --- because it is already contained in the set. traceContained :: Options -> AbsSet -> Expr -> AbsSet traceContained opts q e = traceAbs opts doc q where doc = indent (text "Set contains expression" $$ ppExp e) --- Add a new expression to the set (useful for tracing). --- The expression is renamed before it is added so that the invariant --- that all expressions in the set are renamed is asserted. addNew :: Options -> AbsSet -> Expr -> AbsSet addNew opts q e = traceAbs opts doc (e' : q) where doc = indent (text "Adding expression" $$ ppExp e') e' = normalizeExpr e --- Is an expression already contained in the set of expressions, either as --- a renaming or a constructor instance (modulo normalization)? contained :: Options -> AbsSet -> Expr -> Bool contained _ q e = normalizeExpr e `elem` q --- Abstraction of a list of expressions. absAll :: Options -> AbsSet -> [Expr] -> AbsSet absAll opts q = foldl (abs opts) q --- abstraction for terms not within square brackets. --- See [Albert et al. 1998, p. 803, definition 5.11] abs :: Options -> AbsSet -> Expr -> AbsSet abs _ q (Var _) = q -- ignored abs _ q (Lit _) = q -- ignored abs opts q c@(Comb _ _ es) = case getSQ c of Just e -> absRedex opts q (complete e) _ -> absAll opts q es abs opts q (Let bs e) = absAll opts q (e : map snd bs) abs opts q (Free _ e) = abs opts q e abs opts q (Or e1 e2) = absAll opts q [e1, e2] abs opts q (Case _ e bs) = absAll opts q (e : branchExprs bs) abs opts q (Typed e _) = abs opts q e --- Abstract a reducible expression, i.e., a call to a user-defined function --- or an expression surrounded by square brackets. --- The behaviour is parametric w.r.t. the abstraction mode. absRedex :: Options -> AbsSet -> Expr -> AbsSet absRedex opts q e | isVar e = q | contained opts q e = if not (evaluable e) then abs opts q (sq' e) else traceContained opts q e | otherwise = case optAbstract opts of None -> addNew opts q e WFO -> absWfo opts q e WQO -> absWqo opts q e --- Compute whether an expression is evaluable by the partial evaluation --- semantics at all. In short, evaluable are: --- * variables which are bound in the surrounding context --- * function calls --- * case expressions not applied to an unbound variable --- * let-bindings where the subjacent expression is evaluable --- * Non-determinism where any of the subjacent expressions is evaluable evaluable :: Expr -> Bool evaluable e0 = eval [] e0 where eval vs (Var x) = x `elem` vs eval _ (Lit _) = False eval vs c@(Comb ct _ _) = case getSQ c of Just e -> eval vs e _ -> ct == FuncCall -- || any (not . isVar) es eval vs (Let ds e) = eval (vs ++ map fst ds) e eval vs (Free xs e) = eval (vs ++ xs) e eval vs (Or e1 e2) = eval vs e1 || eval vs e2 -- True? eval _ (Case _ _ _) = True eval vs (Typed e _) = eval vs e --- Complete a partial function call to a regular function call complete :: Expr -> Expr complete e = case e of Comb ct@(FuncPartCall _) qn es -> completePartCall ct qn es _ -> e -- ----------------------------------------------------------------------------- -- Abstraction based on a well-founded ordering (size of expression) -- ----------------------------------------------------------------------------- --- Abstraction based on a well-founded order. absWfo :: Options -> [Expr] -> Expr -> [Expr] absWfo opts q e = case firstComparable e q of Nothing -> addNew opts q e Just c | size e <= size c -> addNew opts q e | otherwise -> absMsg opts q e c --- Find the best comparable expression in a list of expressions --- w.r.t the given expression. A comparable must share the same root expression --- and is the first one found, i.e., the smallest one. firstComparable :: Expr -> [Expr] -> Maybe Expr firstComparable e es = case filter (`comparable` e) es of [] -> Nothing c:_ -> Just c --- Decreasing size property of the sequence. decreasing :: AbsSet -> Bool decreasing [] = True decreasing (e:es) = all (\e' -> size e' >= size e) (filter (`comparable` e) es) && decreasing es --- Size of an expression, inducing a well-founded ordering. --- A variable has a size of zero because we want expression like `1` or `True` --- to be of a greater size since they contain more information. --- Integer literals are translated into constructor terms for termination. size :: Expr -> Int size (Var _) = 0 size (Lit l) = case l of Charc _ -> 1 Floatc _ -> 1 Intc i -> size $ int2Expr i size c@(Comb _ _ es) = case getSQ c of Just e -> size e _ -> 1 + sum (map size es) size (Let bs e) = 1 + sum (map size (e : map snd bs)) size (Free _ e) = 1 + size e size (Or e1 e2) = 1 + size e1 + size e2 size (Case _ e bs) = 1 + sum (map size (e : branchExprs bs)) size (Typed e _) = size e -- ----------------------------------------------------------------------------- -- Abstraction based on a well-quasi ordering (homeomorphic embedding) -- ----------------------------------------------------------------------------- --- abstraction based on a well-quasi order. absWqo :: Options -> [Expr] -> Expr -> [Expr] absWqo opts q e | contained opts q e = traceContained opts q e | otherwise = case embeddedPre e q of Nothing -> addNew opts q e Just e' -> absMsg opts q e e' --- Nonembedding property of the sequence nonembedding :: AbsSet -> Bool nonembedding [] = True nonembedding (e:es) = none (\e' -> embedded e' e) (filter (`comparable` e) es) && nonembedding es --- Well-quasi order based on the embedding. --- Integer literals are translated into constructor terms for termination. embeddedPre :: Expr -> AbsSet -> Maybe Expr embeddedPre e es = find (\e' -> comparable e' e && embedded e' e) es --- Is the first expression embedded in the second expression? embedded :: Expr -> Expr -> Bool embedded e1 e2 = embedded' e1 e2 || embeddedArg e1 e2 --- Is the first expression directly embedded in the second expression, i.e., --- the outermost symbols coincide and the arguments are (not necessarily --- directly) embedded. embedded' :: Expr -> Expr -> Bool embedded' ex1 ex2 = case (ex1, ex2) of (Var _, Var _) -> True (Lit (Charc x), Lit (Charc y)) -> x == y (Lit (Floatc x), Lit (Floatc y)) -> x == y (Lit (Intc x), Lit (Intc y)) -> embedded (int2Expr x) (int2Expr y) (Comb c1 f1 es1, Comb c2 f2 es2) -> c1 == c2 && f1 == f2 && allEmbedded es1 es2 (Let ds1 e1, Let ds2 e2) -> length ds1 <= length ds2 && embedded e1 e2 && allEmbedded' (map snd ds1) (map snd ds2) (Free xs e1, Free ys e2) -> length xs <= length ys && embedded e1 e2 (Or e1 f1, Or e2 f2) -> embedded e1 e2 && embedded f1 f2 (Case c1 e1 bs1, Case c2 e2 bs2) -> c1 == c2 && samePattern bs1 bs2 && allEmbedded (e1 : branchExprs bs1) (e2 : branchExprs bs2) (Typed e1 ty1, Typed e2 ty2) -> ty1 == ty2 && embedded e1 e2 _ -> False --- Are the expressions in the first list embedded in the expression from --- the second list, assuming an equal list length? allEmbedded :: [Expr] -> [Expr] -> Bool allEmbedded xs ys = and (zipWith embedded xs ys) --- Are the expressions in the first list embedded in the expression from --- the second list, assuming that the first list may be shorter --- than the second list? allEmbedded' :: [Expr] -> [Expr] -> Bool allEmbedded' [] _ = True allEmbedded' (_:_) [] = False allEmbedded' (e:es) (e':es') = (embedded e e' && allEmbedded' es es') || allEmbedded' (e:es) es' -- --- Is the first expression strictly embedded in the second expression? -- strictEmbedded :: Expr -> Expr -> Bool -- strictEmbedded e1 e2 = embedded e1 e2 && not (e1 `strictInstanceOf` e2) --- Is the first expression embedded in some sub-expression --- of the second expression? embeddedArg :: Expr -> Expr -> Bool embeddedArg _ (Var _) = False embeddedArg _ (Lit _) = False embeddedArg x (Comb _ _ es) = any (embedded x) es embeddedArg x (Let bs e) = any (embedded x) (e : map snd bs) embeddedArg x (Free _ e) = x `embedded` e embeddedArg x (Or e1 e2) = any (embedded x) [e1, e2] embeddedArg x (Case _ e bs) = any (embedded x) (e : branchExprs bs) embeddedArg x (Typed e _) = x `embedded` e -- ----------------------------------------------------------------------------- -- Auxiliary functions for abstraction -- ----------------------------------------------------------------------------- --- Abstract two expression by computing their most specific generalization. absMsg :: Options -> AbsSet -> Expr -> Expr -> AbsSet absMsg opts q new old = traceAbs opts doc res where res -- There is no specific generalization, thus we add the components of e. -- Because of case-expressions, free variables and let-bindings, it is -- possible that two expressions can not be generalized, for instance -- consider the example `listUni`. | isVar g = traceAbs opts (text "Ignoring variable msg" <+> ppExp g) $ abs opts q (sq' new) -- The expression to add equals the generalization. -- We remove the matching comparable expression to avoid -- a non-terminating loop, and add the generalization -- as well as the range of the substitutions. | eqNorm g new = absAll opts (delete old q) (map sq $ g : rng s1 ++ rng s2) -- We add both the generalization and the range of the substitution. | otherwise = absAll opts q (map sq $ g : rng s1 ++ rng s2) (g, s1, s2) = msg new old doc = vsep $ map indent [ text "Generalizing new expression" $$ ppExp new , text "and old expression" $$ ppExp old , text "to most-specific generalization" $$ ppExp g , text "with substitutions" $$ text "sigma" <+> equals <+> ppSubst s1 $$ text "theta" <+> equals <+> ppSubst s2 ] --- Two expressions are comparable if their root symbols coincide. comparable :: Expr -> Expr -> Bool comparable ex1 ex2 = case (ex1, ex2) of (Var _, Var _) -> True (Lit _, Lit _) -> True (Comb FuncCall a [f,_], Comb FuncCall b [g,_]) -- We don't want to generalize `apply`, therefore we ignore it here. | a == prelApply && b == prelApply -> comparable f g (Comb c1 f1 _, Comb c2 f2 _) -> c1 == c2 && f1 == f2 (Free _ _, Free _ _) -> True (Or _ _, Or _ _) -> True (Case c1 _ bs1, Case c2 _ bs2) -> c1 == c2 && samePattern bs1 bs2 (Let _ _, Let _ _) -> True (Typed _ ty1, Typed _ ty2) -> ty1 == ty2 _ -> False --- Translates an integer literal to a constructor term --- to be able to apply ordering. int2Expr :: Int -> Expr int2Expr x | x < 0 = Comb ConsCall (prelude "-") [int2Expr (-x)] | x < 10 = digit x | otherwise = Comb ConsCall (prelude ":") [digit d, int2Expr m] where digit n = Comb ConsCall (prelude $ show n) [] (d, m) = x `divMod` 10 -- ----------------------------------------------------------------------------- -- closedness test (assertion) -- ----------------------------------------------------------------------------- --- Does every expression in the abstraction set originate from some --- expression in the expression list?list of expressions originateFrom :: AbsSet -> [Expr] -> Bool originateFrom q es = all (\e -> any (\e' -> complete e' `instanceOf` e) es') q where es' = [ e' | e <- es, e' <- subExprs e, not (isVar e') ] --- `allClosed p q es` computes whether all expressions in `es` are closed --- with respect to the sequence `q`, where `p` contains the user-defined --- functions, i.e, functions not contained in `p` are considered primitive. allClosed :: [QName] -> AbsSet -> [Expr] -> Bool allClosed p q es = all (closed p q) es --- `allClosed p q e` computes whether the expression `e` is closed --- with respect to the sequence `q`, where `p` contains the user-defined --- functions, i.e, functions not contained in `p` are considered primitive. closed :: [QName] -> AbsSet -> Expr -> Bool closed p q e = case e of Var _ -> True Lit _ -> True Comb ct qn es | isConsCall ct -> allClosed p q es | otherwise -> case getSQ e of Just e' -> closed p q e' || recClosed q e' _ -> isFailed e || recClosed q e || (isPrimitive p qn && allClosed p q es) Let ds e' -> allClosed p q (e' : map snd ds) || recClosed q e Free _ e' -> closed p q e' || recClosed q e Or e1 e2 -> (closed p q e1 && closed p q e2) || recClosed q e Case _ e' bs -> allClosed p q (e' : branchExprs bs) || recClosed q e Typed e' _ -> recClosed q e || recClosed q e' where recClosed [] _ = False recClosed (q':qs) e' = case instance (complete e') q' of Just s -> allClosed p q (rng s) || recClosed qs e' Nothing -> recClosed qs e' --- Is a function considered primitive, i.e., not contained in the given --- list of user-defined functions? isPrimitive :: [QName] -> QName -> Bool isPrimitive fs qn = qn `notElem` fs