diff options
Diffstat (limited to 'compiler/stranal')
-rw-r--r-- | compiler/stranal/DmdAnal.lhs | 1185 | ||||
-rw-r--r-- | compiler/stranal/SaAbsInt.lhs | 925 | ||||
-rw-r--r-- | compiler/stranal/SaLib.lhs | 130 | ||||
-rw-r--r-- | compiler/stranal/StrictAnal.lhs | 494 | ||||
-rw-r--r-- | compiler/stranal/WorkWrap.lhs | 403 | ||||
-rw-r--r-- | compiler/stranal/WwLib.lhs | 514 |
6 files changed, 3651 insertions, 0 deletions
diff --git a/compiler/stranal/DmdAnal.lhs b/compiler/stranal/DmdAnal.lhs new file mode 100644 index 0000000000..c5cfb7b4bd --- /dev/null +++ b/compiler/stranal/DmdAnal.lhs @@ -0,0 +1,1185 @@ +% +% (c) The GRASP/AQUA Project, Glasgow University, 1993-1998 +% + + ----------------- + A demand analysis + ----------------- + +\begin{code} +module DmdAnal ( dmdAnalPgm, dmdAnalTopRhs, + both {- needed by WwLib -} + ) where + +#include "HsVersions.h" + +import DynFlags ( DynFlags, DynFlag(..) ) +import StaticFlags ( opt_MaxWorkerArgs ) +import NewDemand -- All of it +import CoreSyn +import PprCore +import CoreUtils ( exprIsHNF, exprIsTrivial, exprArity ) +import DataCon ( dataConTyCon ) +import TyCon ( isProductTyCon, isRecursiveTyCon ) +import Id ( Id, idType, idInlinePragma, + isDataConWorkId, isGlobalId, idArity, +#ifdef OLD_STRICTNESS + idDemandInfo, idStrictness, idCprInfo, idName, +#endif + idNewStrictness, idNewStrictness_maybe, + setIdNewStrictness, idNewDemandInfo, + idNewDemandInfo_maybe, + setIdNewDemandInfo + ) +#ifdef OLD_STRICTNESS +import IdInfo ( newStrictnessFromOld, newDemand ) +#endif +import Var ( Var ) +import VarEnv +import TysWiredIn ( unboxedPairDataCon ) +import TysPrim ( realWorldStatePrimTy ) +import UniqFM ( plusUFM_C, addToUFM_Directly, lookupUFM_Directly, + keysUFM, minusUFM, ufmToList, filterUFM ) +import Type ( isUnLiftedType, coreEqType ) +import CoreLint ( showPass, endPass ) +import Util ( mapAndUnzip, mapAccumL, mapAccumR, lengthIs ) +import BasicTypes ( Arity, TopLevelFlag(..), isTopLevel, isNeverActive, + RecFlag(..), isRec ) +import Maybes ( orElse, expectJust ) +import Outputable +\end{code} + +To think about + +* set a noinline pragma on bottoming Ids + +* Consider f x = x+1 `fatbar` error (show x) + We'd like to unbox x, even if that means reboxing it in the error case. + + +%************************************************************************ +%* * +\subsection{Top level stuff} +%* * +%************************************************************************ + +\begin{code} +dmdAnalPgm :: DynFlags -> [CoreBind] -> IO [CoreBind] +dmdAnalPgm dflags binds + = do { + showPass dflags "Demand analysis" ; + let { binds_plus_dmds = do_prog binds } ; + + endPass dflags "Demand analysis" + Opt_D_dump_stranal binds_plus_dmds ; +#ifdef OLD_STRICTNESS + -- Only if OLD_STRICTNESS is on, because only then is the old + -- strictness analyser run + let { dmd_changes = get_changes binds_plus_dmds } ; + printDump (text "Changes in demands" $$ dmd_changes) ; +#endif + return binds_plus_dmds + } + where + do_prog :: [CoreBind] -> [CoreBind] + do_prog binds = snd $ mapAccumL dmdAnalTopBind emptySigEnv binds + +dmdAnalTopBind :: SigEnv + -> CoreBind + -> (SigEnv, CoreBind) +dmdAnalTopBind sigs (NonRec id rhs) + = let + ( _, _, (_, rhs1)) = dmdAnalRhs TopLevel NonRecursive sigs (id, rhs) + (sigs2, _, (id2, rhs2)) = dmdAnalRhs TopLevel NonRecursive sigs (id, rhs1) + -- Do two passes to improve CPR information + -- See comments with ignore_cpr_info in mk_sig_ty + -- and with extendSigsWithLam + in + (sigs2, NonRec id2 rhs2) + +dmdAnalTopBind sigs (Rec pairs) + = let + (sigs', _, pairs') = dmdFix TopLevel sigs pairs + -- We get two iterations automatically + -- c.f. the NonRec case above + in + (sigs', Rec pairs') +\end{code} + +\begin{code} +dmdAnalTopRhs :: CoreExpr -> (StrictSig, CoreExpr) +-- Analyse the RHS and return +-- a) appropriate strictness info +-- b) the unfolding (decorated with stricntess info) +dmdAnalTopRhs rhs + = (sig, rhs2) + where + call_dmd = vanillaCall (exprArity rhs) + (_, rhs1) = dmdAnal emptySigEnv call_dmd rhs + (rhs_ty, rhs2) = dmdAnal emptySigEnv call_dmd rhs1 + sig = mkTopSigTy rhs rhs_ty + -- Do two passes; see notes with extendSigsWithLam + -- Otherwise we get bogus CPR info for constructors like + -- newtype T a = MkT a + -- The constructor looks like (\x::T a -> x), modulo the coerce + -- extendSigsWithLam will optimistically give x a CPR tag the + -- first time, which is wrong in the end. +\end{code} + +%************************************************************************ +%* * +\subsection{The analyser itself} +%* * +%************************************************************************ + +\begin{code} +dmdAnal :: SigEnv -> Demand -> CoreExpr -> (DmdType, CoreExpr) + +dmdAnal sigs Abs e = (topDmdType, e) + +dmdAnal sigs dmd e + | not (isStrictDmd dmd) + = let + (res_ty, e') = dmdAnal sigs evalDmd e + in + (deferType res_ty, e') + -- It's important not to analyse e with a lazy demand because + -- a) When we encounter case s of (a,b) -> + -- we demand s with U(d1d2)... but if the overall demand is lazy + -- that is wrong, and we'd need to reduce the demand on s, + -- which is inconvenient + -- b) More important, consider + -- f (let x = R in x+x), where f is lazy + -- We still want to mark x as demanded, because it will be when we + -- enter the let. If we analyse f's arg with a Lazy demand, we'll + -- just mark x as Lazy + -- c) The application rule wouldn't be right either + -- Evaluating (f x) in a L demand does *not* cause + -- evaluation of f in a C(L) demand! + + +dmdAnal sigs dmd (Lit lit) + = (topDmdType, Lit lit) + +dmdAnal sigs dmd (Var var) + = (dmdTransform sigs var dmd, Var var) + +dmdAnal sigs dmd (Note n e) + = (dmd_ty, Note n e') + where + (dmd_ty, e') = dmdAnal sigs dmd' e + dmd' = case n of + Coerce _ _ -> evalDmd -- This coerce usually arises from a recursive + other -> dmd -- newtype, and we don't want to look inside them + -- for exactly the same reason that we don't look + -- inside recursive products -- we might not reach + -- a fixpoint. So revert to a vanilla Eval demand + +dmdAnal sigs dmd (App fun (Type ty)) + = (fun_ty, App fun' (Type ty)) + where + (fun_ty, fun') = dmdAnal sigs dmd fun + +-- Lots of the other code is there to make this +-- beautiful, compositional, application rule :-) +dmdAnal sigs dmd e@(App fun arg) -- Non-type arguments + = let -- [Type arg handled above] + (fun_ty, fun') = dmdAnal sigs (Call dmd) fun + (arg_ty, arg') = dmdAnal sigs arg_dmd arg + (arg_dmd, res_ty) = splitDmdTy fun_ty + in + (res_ty `bothType` arg_ty, App fun' arg') + +dmdAnal sigs dmd (Lam var body) + | isTyVar var + = let + (body_ty, body') = dmdAnal sigs dmd body + in + (body_ty, Lam var body') + + | Call body_dmd <- dmd -- A call demand: good! + = let + sigs' = extendSigsWithLam sigs var + (body_ty, body') = dmdAnal sigs' body_dmd body + (lam_ty, var') = annotateLamIdBndr body_ty var + in + (lam_ty, Lam var' body') + + | otherwise -- Not enough demand on the lambda; but do the body + = let -- anyway to annotate it and gather free var info + (body_ty, body') = dmdAnal sigs evalDmd body + (lam_ty, var') = annotateLamIdBndr body_ty var + in + (deferType lam_ty, Lam var' body') + +dmdAnal sigs dmd (Case scrut case_bndr ty [alt@(DataAlt dc,bndrs,rhs)]) + | let tycon = dataConTyCon dc, + isProductTyCon tycon, + not (isRecursiveTyCon tycon) + = let + sigs_alt = extendSigEnv NotTopLevel sigs case_bndr case_bndr_sig + (alt_ty, alt') = dmdAnalAlt sigs_alt dmd alt + (alt_ty1, case_bndr') = annotateBndr alt_ty case_bndr + (_, bndrs', _) = alt' + case_bndr_sig = cprSig + -- Inside the alternative, the case binder has the CPR property. + -- Meaning that a case on it will successfully cancel. + -- Example: + -- f True x = case x of y { I# x' -> if x' ==# 3 then y else I# 8 } + -- f False x = I# 3 + -- + -- We want f to have the CPR property: + -- f b x = case fw b x of { r -> I# r } + -- fw True x = case x of y { I# x' -> if x' ==# 3 then x' else 8 } + -- fw False x = 3 + + -- Figure out whether the demand on the case binder is used, and use + -- that to set the scrut_dmd. This is utterly essential. + -- Consider f x = case x of y { (a,b) -> k y a } + -- If we just take scrut_demand = U(L,A), then we won't pass x to the + -- worker, so the worker will rebuild + -- x = (a, absent-error) + -- and that'll crash. + -- So at one stage I had: + -- dead_case_bndr = isAbsentDmd (idNewDemandInfo case_bndr') + -- keepity | dead_case_bndr = Drop + -- | otherwise = Keep + -- + -- But then consider + -- case x of y { (a,b) -> h y + a } + -- where h : U(LL) -> T + -- The above code would compute a Keep for x, since y is not Abs, which is silly + -- The insight is, of course, that a demand on y is a demand on the + -- scrutinee, so we need to `both` it with the scrut demand + + scrut_dmd = Eval (Prod [idNewDemandInfo b | b <- bndrs', isId b]) + `both` + idNewDemandInfo case_bndr' + + (scrut_ty, scrut') = dmdAnal sigs scrut_dmd scrut + in + (alt_ty1 `bothType` scrut_ty, Case scrut' case_bndr' ty [alt']) + +dmdAnal sigs dmd (Case scrut case_bndr ty alts) + = let + (alt_tys, alts') = mapAndUnzip (dmdAnalAlt sigs dmd) alts + (scrut_ty, scrut') = dmdAnal sigs evalDmd scrut + (alt_ty, case_bndr') = annotateBndr (foldr1 lubType alt_tys) case_bndr + in +-- pprTrace "dmdAnal:Case" (ppr alts $$ ppr alt_tys) + (alt_ty `bothType` scrut_ty, Case scrut' case_bndr' ty alts') + +dmdAnal sigs dmd (Let (NonRec id rhs) body) + = let + (sigs', lazy_fv, (id1, rhs')) = dmdAnalRhs NotTopLevel NonRecursive sigs (id, rhs) + (body_ty, body') = dmdAnal sigs' dmd body + (body_ty1, id2) = annotateBndr body_ty id1 + body_ty2 = addLazyFVs body_ty1 lazy_fv + in + -- If the actual demand is better than the vanilla call + -- demand, you might think that we might do better to re-analyse + -- the RHS with the stronger demand. + -- But (a) That seldom happens, because it means that *every* path in + -- the body of the let has to use that stronger demand + -- (b) It often happens temporarily in when fixpointing, because + -- the recursive function at first seems to place a massive demand. + -- But we don't want to go to extra work when the function will + -- probably iterate to something less demanding. + -- In practice, all the times the actual demand on id2 is more than + -- the vanilla call demand seem to be due to (b). So we don't + -- bother to re-analyse the RHS. + (body_ty2, Let (NonRec id2 rhs') body') + +dmdAnal sigs dmd (Let (Rec pairs) body) + = let + bndrs = map fst pairs + (sigs', lazy_fv, pairs') = dmdFix NotTopLevel sigs pairs + (body_ty, body') = dmdAnal sigs' dmd body + body_ty1 = addLazyFVs body_ty lazy_fv + in + sigs' `seq` body_ty `seq` + let + (body_ty2, _) = annotateBndrs body_ty1 bndrs + -- Don't bother to add demand info to recursive + -- binders as annotateBndr does; + -- being recursive, we can't treat them strictly. + -- But we do need to remove the binders from the result demand env + in + (body_ty2, Let (Rec pairs') body') + + +dmdAnalAlt sigs dmd (con,bndrs,rhs) + = let + (rhs_ty, rhs') = dmdAnal sigs dmd rhs + (alt_ty, bndrs') = annotateBndrs rhs_ty bndrs + final_alt_ty | io_hack_reqd = alt_ty `lubType` topDmdType + | otherwise = alt_ty + + -- There's a hack here for I/O operations. Consider + -- case foo x s of { (# s, r #) -> y } + -- Is this strict in 'y'. Normally yes, but what if 'foo' is an I/O + -- operation that simply terminates the program (not in an erroneous way)? + -- In that case we should not evaluate y before the call to 'foo'. + -- Hackish solution: spot the IO-like situation and add a virtual branch, + -- as if we had + -- case foo x s of + -- (# s, r #) -> y + -- other -> return () + -- So the 'y' isn't necessarily going to be evaluated + -- + -- A more complete example where this shows up is: + -- do { let len = <expensive> ; + -- ; when (...) (exitWith ExitSuccess) + -- ; print len } + + io_hack_reqd = con == DataAlt unboxedPairDataCon && + idType (head bndrs) `coreEqType` realWorldStatePrimTy + in + (final_alt_ty, (con, bndrs', rhs')) +\end{code} + +%************************************************************************ +%* * +\subsection{Bindings} +%* * +%************************************************************************ + +\begin{code} +dmdFix :: TopLevelFlag + -> SigEnv -- Does not include bindings for this binding + -> [(Id,CoreExpr)] + -> (SigEnv, DmdEnv, + [(Id,CoreExpr)]) -- Binders annotated with stricness info + +dmdFix top_lvl sigs orig_pairs + = loop 1 initial_sigs orig_pairs + where + bndrs = map fst orig_pairs + initial_sigs = extendSigEnvList sigs [(id, (initialSig id, top_lvl)) | id <- bndrs] + + loop :: Int + -> SigEnv -- Already contains the current sigs + -> [(Id,CoreExpr)] + -> (SigEnv, DmdEnv, [(Id,CoreExpr)]) + loop n sigs pairs + | found_fixpoint + = (sigs', lazy_fv, pairs') + -- Note: use pairs', not pairs. pairs' is the result of + -- processing the RHSs with sigs (= sigs'), whereas pairs + -- is the result of processing the RHSs with the *previous* + -- iteration of sigs. + + | n >= 10 = pprTrace "dmdFix loop" (ppr n <+> (vcat + [ text "Sigs:" <+> ppr [(id,lookup sigs id, lookup sigs' id) | (id,_) <- pairs], + text "env:" <+> ppr (ufmToList sigs), + text "binds:" <+> pprCoreBinding (Rec pairs)])) + (emptySigEnv, lazy_fv, orig_pairs) -- Safe output + -- The lazy_fv part is really important! orig_pairs has no strictness + -- info, including nothing about free vars. But if we have + -- letrec f = ....y..... in ...f... + -- where 'y' is free in f, we must record that y is mentioned, + -- otherwise y will get recorded as absent altogether + + | otherwise = loop (n+1) sigs' pairs' + where + found_fixpoint = all (same_sig sigs sigs') bndrs + -- Use the new signature to do the next pair + -- The occurrence analyser has arranged them in a good order + -- so this can significantly reduce the number of iterations needed + ((sigs',lazy_fv), pairs') = mapAccumL (my_downRhs top_lvl) (sigs, emptyDmdEnv) pairs + + my_downRhs top_lvl (sigs,lazy_fv) (id,rhs) + = -- pprTrace "downRhs {" (ppr id <+> (ppr old_sig)) + -- (new_sig `seq` + -- pprTrace "downRhsEnd" (ppr id <+> ppr new_sig <+> char '}' ) + ((sigs', lazy_fv'), pair') + -- ) + where + (sigs', lazy_fv1, pair') = dmdAnalRhs top_lvl Recursive sigs (id,rhs) + lazy_fv' = plusUFM_C both lazy_fv lazy_fv1 + -- old_sig = lookup sigs id + -- new_sig = lookup sigs' id + + same_sig sigs sigs' var = lookup sigs var == lookup sigs' var + lookup sigs var = case lookupVarEnv sigs var of + Just (sig,_) -> sig + + -- Get an initial strictness signature from the Id + -- itself. That way we make use of earlier iterations + -- of the fixpoint algorithm. (Cunning plan.) + -- Note that the cunning plan extends to the DmdEnv too, + -- since it is part of the strictness signature +initialSig id = idNewStrictness_maybe id `orElse` botSig + +dmdAnalRhs :: TopLevelFlag -> RecFlag + -> SigEnv -> (Id, CoreExpr) + -> (SigEnv, DmdEnv, (Id, CoreExpr)) +-- Process the RHS of the binding, add the strictness signature +-- to the Id, and augment the environment with the signature as well. + +dmdAnalRhs top_lvl rec_flag sigs (id, rhs) + = (sigs', lazy_fv, (id', rhs')) + where + arity = idArity id -- The idArity should be up to date + -- The simplifier was run just beforehand + (rhs_dmd_ty, rhs') = dmdAnal sigs (vanillaCall arity) rhs + (lazy_fv, sig_ty) = WARN( arity /= dmdTypeDepth rhs_dmd_ty && not (exprIsTrivial rhs), ppr id ) + -- The RHS can be eta-reduced to just a variable, + -- in which case we should not complain. + mkSigTy top_lvl rec_flag id rhs rhs_dmd_ty + id' = id `setIdNewStrictness` sig_ty + sigs' = extendSigEnv top_lvl sigs id sig_ty +\end{code} + +%************************************************************************ +%* * +\subsection{Strictness signatures and types} +%* * +%************************************************************************ + +\begin{code} +mkTopSigTy :: CoreExpr -> DmdType -> StrictSig + -- Take a DmdType and turn it into a StrictSig + -- NB: not used for never-inline things; hence False +mkTopSigTy rhs dmd_ty = snd (mk_sig_ty False False rhs dmd_ty) + +mkSigTy :: TopLevelFlag -> RecFlag -> Id -> CoreExpr -> DmdType -> (DmdEnv, StrictSig) +mkSigTy top_lvl rec_flag id rhs dmd_ty + = mk_sig_ty never_inline thunk_cpr_ok rhs dmd_ty + where + never_inline = isNeverActive (idInlinePragma id) + maybe_id_dmd = idNewDemandInfo_maybe id + -- Is Nothing the first time round + + thunk_cpr_ok + | isTopLevel top_lvl = False -- Top level things don't get + -- their demandInfo set at all + | isRec rec_flag = False -- Ditto recursive things + | Just dmd <- maybe_id_dmd = isStrictDmd dmd + | otherwise = True -- Optimistic, first time round + -- See notes below +\end{code} + +The thunk_cpr_ok stuff [CPR-AND-STRICTNESS] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If the rhs is a thunk, we usually forget the CPR info, because +it is presumably shared (else it would have been inlined, and +so we'd lose sharing if w/w'd it into a function. + +However, if the strictness analyser has figured out (in a previous +iteration) that it's strict, then we DON'T need to forget the CPR info. +Instead we can retain the CPR info and do the thunk-splitting transform +(see WorkWrap.splitThunk). + +This made a big difference to PrelBase.modInt, which had something like + modInt = \ x -> let r = ... -> I# v in + ...body strict in r... +r's RHS isn't a value yet; but modInt returns r in various branches, so +if r doesn't have the CPR property then neither does modInt +Another case I found in practice (in Complex.magnitude), looks like this: + let k = if ... then I# a else I# b + in ... body strict in k .... +(For this example, it doesn't matter whether k is returned as part of +the overall result; but it does matter that k's RHS has the CPR property.) +Left to itself, the simplifier will make a join point thus: + let $j k = ...body strict in k... + if ... then $j (I# a) else $j (I# b) +With thunk-splitting, we get instead + let $j x = let k = I#x in ...body strict in k... + in if ... then $j a else $j b +This is much better; there's a good chance the I# won't get allocated. + +The difficulty with this is that we need the strictness type to +look at the body... but we now need the body to calculate the demand +on the variable, so we can decide whether its strictness type should +have a CPR in it or not. Simple solution: + a) use strictness info from the previous iteration + b) make sure we do at least 2 iterations, by doing a second + round for top-level non-recs. Top level recs will get at + least 2 iterations except for totally-bottom functions + which aren't very interesting anyway. + +NB: strictly_demanded is never true of a top-level Id, or of a recursive Id. + +The Nothing case in thunk_cpr_ok [CPR-AND-STRICTNESS] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Demand info now has a 'Nothing' state, just like strictness info. +The analysis works from 'dangerous' towards a 'safe' state; so we +start with botSig for 'Nothing' strictness infos, and we start with +"yes, it's demanded" for 'Nothing' in the demand info. The +fixpoint iteration will sort it all out. + +We can't start with 'not-demanded' because then consider + f x = let + t = ... I# x + in + if ... then t else I# y else f x' + +In the first iteration we'd have no demand info for x, so assume +not-demanded; then we'd get TopRes for f's CPR info. Next iteration +we'd see that t was demanded, and so give it the CPR property, but by +now f has TopRes, so it will stay TopRes. Instead, with the Nothing +setting the first time round, we say 'yes t is demanded' the first +time. + +However, this does mean that for non-recursive bindings we must +iterate twice to be sure of not getting over-optimistic CPR info, +in the case where t turns out to be not-demanded. This is handled +by dmdAnalTopBind. + + +\begin{code} +mk_sig_ty never_inline thunk_cpr_ok rhs (DmdType fv dmds res) + | never_inline && not (isBotRes res) + -- HACK ALERT + -- Don't strictness-analyse NOINLINE things. Why not? Because + -- the NOINLINE says "don't expose any of the inner workings at the call + -- site" and the strictness is certainly an inner working. + -- + -- More concretely, the demand analyser discovers the following strictness + -- for unsafePerformIO: C(U(AV)) + -- But then consider + -- unsafePerformIO (\s -> let r = f x in + -- case writeIORef v r s of (# s1, _ #) -> + -- (# s1, r #) + -- The strictness analyser will find that the binding for r is strict, + -- (becuase of uPIO's strictness sig), and so it'll evaluate it before + -- doing the writeIORef. This actually makes tests/lib/should_run/memo002 + -- get a deadlock! + -- + -- Solution: don't expose the strictness of unsafePerformIO. + -- + -- But we do want to expose the strictness of error functions, + -- which are also often marked NOINLINE + -- {-# NOINLINE foo #-} + -- foo x = error ("wubble buggle" ++ x) + -- So (hack, hack) we only drop the strictness for non-bottom things + -- This is all very unsatisfactory. + = (deferEnv fv, topSig) + + | otherwise + = (lazy_fv, mkStrictSig dmd_ty) + where + dmd_ty = DmdType strict_fv final_dmds res' + + lazy_fv = filterUFM (not . isStrictDmd) fv + strict_fv = filterUFM isStrictDmd fv + -- We put the strict FVs in the DmdType of the Id, so + -- that at its call sites we unleash demands on its strict fvs. + -- An example is 'roll' in imaginary/wheel-sieve2 + -- Something like this: + -- roll x = letrec + -- go y = if ... then roll (x-1) else x+1 + -- in + -- go ms + -- We want to see that roll is strict in x, which is because + -- go is called. So we put the DmdEnv for x in go's DmdType. + -- + -- Another example: + -- f :: Int -> Int -> Int + -- f x y = let t = x+1 + -- h z = if z==0 then t else + -- if z==1 then x+1 else + -- x + h (z-1) + -- in + -- h y + -- Calling h does indeed evaluate x, but we can only see + -- that if we unleash a demand on x at the call site for t. + -- + -- Incidentally, here's a place where lambda-lifting h would + -- lose the cigar --- we couldn't see the joint strictness in t/x + -- + -- ON THE OTHER HAND + -- We don't want to put *all* the fv's from the RHS into the + -- DmdType, because that makes fixpointing very slow --- the + -- DmdType gets full of lazy demands that are slow to converge. + + final_dmds = setUnpackStrategy dmds + -- Set the unpacking strategy + + res' = case res of + RetCPR | ignore_cpr_info -> TopRes + other -> res + ignore_cpr_info = not (exprIsHNF rhs || thunk_cpr_ok) +\end{code} + +The unpack strategy determines whether we'll *really* unpack the argument, +or whether we'll just remember its strictness. If unpacking would give +rise to a *lot* of worker args, we may decide not to unpack after all. + +\begin{code} +setUnpackStrategy :: [Demand] -> [Demand] +setUnpackStrategy ds + = snd (go (opt_MaxWorkerArgs - nonAbsentArgs ds) ds) + where + go :: Int -- Max number of args available for sub-components of [Demand] + -> [Demand] + -> (Int, [Demand]) -- Args remaining after subcomponents of [Demand] are unpacked + + go n (Eval (Prod cs) : ds) + | n' >= 0 = Eval (Prod cs') `cons` go n'' ds + | otherwise = Box (Eval (Prod cs)) `cons` go n ds + where + (n'',cs') = go n' cs + n' = n + 1 - non_abs_args + -- Add one to the budget 'cos we drop the top-level arg + non_abs_args = nonAbsentArgs cs + -- Delete # of non-absent args to which we'll now be committed + + go n (d:ds) = d `cons` go n ds + go n [] = (n,[]) + + cons d (n,ds) = (n, d:ds) + +nonAbsentArgs :: [Demand] -> Int +nonAbsentArgs [] = 0 +nonAbsentArgs (Abs : ds) = nonAbsentArgs ds +nonAbsentArgs (d : ds) = 1 + nonAbsentArgs ds +\end{code} + + +%************************************************************************ +%* * +\subsection{Strictness signatures and types} +%* * +%************************************************************************ + +\begin{code} +splitDmdTy :: DmdType -> (Demand, DmdType) +-- Split off one function argument +-- We already have a suitable demand on all +-- free vars, so no need to add more! +splitDmdTy (DmdType fv (dmd:dmds) res_ty) = (dmd, DmdType fv dmds res_ty) +splitDmdTy ty@(DmdType fv [] res_ty) = (resTypeArgDmd res_ty, ty) +\end{code} + +\begin{code} +unitVarDmd var dmd = DmdType (unitVarEnv var dmd) [] TopRes + +addVarDmd top_lvl dmd_ty@(DmdType fv ds res) var dmd + | isTopLevel top_lvl = dmd_ty -- Don't record top level things + | otherwise = DmdType (extendVarEnv fv var dmd) ds res + +addLazyFVs (DmdType fv ds res) lazy_fvs + = DmdType both_fv1 ds res + where + both_fv = (plusUFM_C both fv lazy_fvs) + both_fv1 = modifyEnv (isBotRes res) (`both` Bot) lazy_fvs fv both_fv + -- This modifyEnv is vital. Consider + -- let f = \x -> (x,y) + -- in error (f 3) + -- Here, y is treated as a lazy-fv of f, but we must `both` that L + -- demand with the bottom coming up from 'error' + -- + -- I got a loop in the fixpointer without this, due to an interaction + -- with the lazy_fv filtering in mkSigTy. Roughly, it was + -- letrec f n x + -- = letrec g y = x `fatbar` + -- letrec h z = z + ...g... + -- in h (f (n-1) x) + -- in ... + -- In the initial iteration for f, f=Bot + -- Suppose h is found to be strict in z, but the occurrence of g in its RHS + -- is lazy. Now consider the fixpoint iteration for g, esp the demands it + -- places on its free variables. Suppose it places none. Then the + -- x `fatbar` ...call to h... + -- will give a x->V demand for x. That turns into a L demand for x, + -- which floats out of the defn for h. Without the modifyEnv, that + -- L demand doesn't get both'd with the Bot coming up from the inner + -- call to f. So we just get an L demand for x for g. + -- + -- A better way to say this is that the lazy-fv filtering should give the + -- same answer as putting the lazy fv demands in the function's type. + +annotateBndr :: DmdType -> Var -> (DmdType, Var) +-- The returned env has the var deleted +-- The returned var is annotated with demand info +-- No effect on the argument demands +annotateBndr dmd_ty@(DmdType fv ds res) var + | isTyVar var = (dmd_ty, var) + | otherwise = (DmdType fv' ds res, setIdNewDemandInfo var dmd) + where + (fv', dmd) = removeFV fv var res + +annotateBndrs = mapAccumR annotateBndr + +annotateLamIdBndr dmd_ty@(DmdType fv ds res) id +-- For lambdas we add the demand to the argument demands +-- Only called for Ids + = ASSERT( isId id ) + (DmdType fv' (hacked_dmd:ds) res, setIdNewDemandInfo id hacked_dmd) + where + (fv', dmd) = removeFV fv id res + hacked_dmd = argDemand dmd + -- This call to argDemand is vital, because otherwise we label + -- a lambda binder with demand 'B'. But in terms of calling + -- conventions that's Abs, because we don't pass it. But + -- when we do a w/w split we get + -- fw x = (\x y:B -> ...) x (error "oops") + -- And then the simplifier things the 'B' is a strict demand + -- and evaluates the (error "oops"). Sigh + +removeFV fv id res = (fv', zapUnlifted id dmd) + where + fv' = fv `delVarEnv` id + dmd = lookupVarEnv fv id `orElse` deflt + deflt | isBotRes res = Bot + | otherwise = Abs + +-- For unlifted-type variables, we are only +-- interested in Bot/Abs/Box Abs +zapUnlifted is Bot = Bot +zapUnlifted id Abs = Abs +zapUnlifted id dmd | isUnLiftedType (idType id) = lazyDmd + | otherwise = dmd +\end{code} + +%************************************************************************ +%* * +\subsection{Strictness signatures} +%* * +%************************************************************************ + +\begin{code} +type SigEnv = VarEnv (StrictSig, TopLevelFlag) + -- We use the SigEnv to tell us whether to + -- record info about a variable in the DmdEnv + -- We do so if it's a LocalId, but not top-level + -- + -- The DmdEnv gives the demand on the free vars of the function + -- when it is given enough args to satisfy the strictness signature + +emptySigEnv = emptyVarEnv + +extendSigEnv :: TopLevelFlag -> SigEnv -> Id -> StrictSig -> SigEnv +extendSigEnv top_lvl env var sig = extendVarEnv env var (sig, top_lvl) + +extendSigEnvList = extendVarEnvList + +extendSigsWithLam :: SigEnv -> Id -> SigEnv +-- Extend the SigEnv when we meet a lambda binder +-- If the binder is marked demanded with a product demand, then give it a CPR +-- signature, because in the likely event that this is a lambda on a fn defn +-- [we only use this when the lambda is being consumed with a call demand], +-- it'll be w/w'd and so it will be CPR-ish. E.g. +-- f = \x::(Int,Int). if ...strict in x... then +-- x +-- else +-- (a,b) +-- We want f to have the CPR property because x does, by the time f has been w/w'd +-- +-- Also note that we only want to do this for something that +-- definitely has product type, else we may get over-optimistic +-- CPR results (e.g. from \x -> x!). + +extendSigsWithLam sigs id + = case idNewDemandInfo_maybe id of + Nothing -> extendVarEnv sigs id (cprSig, NotTopLevel) + -- Optimistic in the Nothing case; + -- See notes [CPR-AND-STRICTNESS] + Just (Eval (Prod ds)) -> extendVarEnv sigs id (cprSig, NotTopLevel) + other -> sigs + + +dmdTransform :: SigEnv -- The strictness environment + -> Id -- The function + -> Demand -- The demand on the function + -> DmdType -- The demand type of the function in this context + -- Returned DmdEnv includes the demand on + -- this function plus demand on its free variables + +dmdTransform sigs var dmd + +------ DATA CONSTRUCTOR + | isDataConWorkId var -- Data constructor + = let + StrictSig dmd_ty = idNewStrictness var -- It must have a strictness sig + DmdType _ _ con_res = dmd_ty + arity = idArity var + in + if arity == call_depth then -- Saturated, so unleash the demand + let + -- Important! If we Keep the constructor application, then + -- we need the demands the constructor places (always lazy) + -- If not, we don't need to. For example: + -- f p@(x,y) = (p,y) -- S(AL) + -- g a b = f (a,b) + -- It's vital that we don't calculate Absent for a! + dmd_ds = case res_dmd of + Box (Eval ds) -> mapDmds box ds + Eval ds -> ds + other -> Poly Top + + -- ds can be empty, when we are just seq'ing the thing + -- If so we must make up a suitable bunch of demands + arg_ds = case dmd_ds of + Poly d -> replicate arity d + Prod ds -> ASSERT( ds `lengthIs` arity ) ds + + in + mkDmdType emptyDmdEnv arg_ds con_res + -- Must remember whether it's a product, hence con_res, not TopRes + else + topDmdType + +------ IMPORTED FUNCTION + | isGlobalId var, -- Imported function + let StrictSig dmd_ty = idNewStrictness var + = if dmdTypeDepth dmd_ty <= call_depth then -- Saturated, so unleash the demand + dmd_ty + else + topDmdType + +------ LOCAL LET/REC BOUND THING + | Just (StrictSig dmd_ty, top_lvl) <- lookupVarEnv sigs var + = let + fn_ty | dmdTypeDepth dmd_ty <= call_depth = dmd_ty + | otherwise = deferType dmd_ty + -- NB: it's important to use deferType, and not just return topDmdType + -- Consider let { f x y = p + x } in f 1 + -- The application isn't saturated, but we must nevertheless propagate + -- a lazy demand for p! + in + addVarDmd top_lvl fn_ty var dmd + +------ LOCAL NON-LET/REC BOUND THING + | otherwise -- Default case + = unitVarDmd var dmd + + where + (call_depth, res_dmd) = splitCallDmd dmd +\end{code} + + +%************************************************************************ +%* * +\subsection{Demands} +%* * +%************************************************************************ + +\begin{code} +splitCallDmd :: Demand -> (Int, Demand) +splitCallDmd (Call d) = case splitCallDmd d of + (n, r) -> (n+1, r) +splitCallDmd d = (0, d) + +vanillaCall :: Arity -> Demand +vanillaCall 0 = evalDmd +vanillaCall n = Call (vanillaCall (n-1)) + +deferType :: DmdType -> DmdType +deferType (DmdType fv _ _) = DmdType (deferEnv fv) [] TopRes + -- Notice that we throw away info about both arguments and results + -- For example, f = let ... in \x -> x + -- We don't want to get a stricness type V->T for f. + -- Peter?? + +deferEnv :: DmdEnv -> DmdEnv +deferEnv fv = mapVarEnv defer fv + + +---------------- +argDemand :: Demand -> Demand +-- The 'Defer' demands are just Lazy at function boundaries +-- Ugly! Ask John how to improve it. +argDemand Top = lazyDmd +argDemand (Defer d) = lazyDmd +argDemand (Eval ds) = Eval (mapDmds argDemand ds) +argDemand (Box Bot) = evalDmd +argDemand (Box d) = box (argDemand d) +argDemand Bot = Abs -- Don't pass args that are consumed (only) by bottom +argDemand d = d +\end{code} + +\begin{code} +------------------------- +-- Consider (if x then y else []) with demand V +-- Then the first branch gives {y->V} and the second +-- *implicitly* has {y->A}. So we must put {y->(V `lub` A)} +-- in the result env. +lubType (DmdType fv1 ds1 r1) (DmdType fv2 ds2 r2) + = DmdType lub_fv2 (lub_ds ds1 ds2) (r1 `lubRes` r2) + where + lub_fv = plusUFM_C lub fv1 fv2 + lub_fv1 = modifyEnv (not (isBotRes r1)) absLub fv2 fv1 lub_fv + lub_fv2 = modifyEnv (not (isBotRes r2)) absLub fv1 fv2 lub_fv1 + -- lub is the identity for Bot + + -- Extend the shorter argument list to match the longer + lub_ds (d1:ds1) (d2:ds2) = lub d1 d2 : lub_ds ds1 ds2 + lub_ds [] [] = [] + lub_ds ds1 [] = map (`lub` resTypeArgDmd r2) ds1 + lub_ds [] ds2 = map (resTypeArgDmd r1 `lub`) ds2 + +----------------------------------- +-- (t1 `bothType` t2) takes the argument/result info from t1, +-- using t2 just for its free-var info +-- NB: Don't forget about r2! It might be BotRes, which is +-- a bottom demand on all the in-scope variables. +-- Peter: can this be done more neatly? +bothType (DmdType fv1 ds1 r1) (DmdType fv2 ds2 r2) + = DmdType both_fv2 ds1 (r1 `bothRes` r2) + where + both_fv = plusUFM_C both fv1 fv2 + both_fv1 = modifyEnv (isBotRes r1) (`both` Bot) fv2 fv1 both_fv + both_fv2 = modifyEnv (isBotRes r2) (`both` Bot) fv1 fv2 both_fv1 + -- both is the identity for Abs +\end{code} + + +\begin{code} +lubRes BotRes r = r +lubRes r BotRes = r +lubRes RetCPR RetCPR = RetCPR +lubRes r1 r2 = TopRes + +-- If either diverges, the whole thing does +-- Otherwise take CPR info from the first +bothRes r1 BotRes = BotRes +bothRes r1 r2 = r1 +\end{code} + +\begin{code} +modifyEnv :: Bool -- No-op if False + -> (Demand -> Demand) -- The zapper + -> DmdEnv -> DmdEnv -- Env1 and Env2 + -> DmdEnv -> DmdEnv -- Transform this env + -- Zap anything in Env1 but not in Env2 + -- Assume: dom(env) includes dom(Env1) and dom(Env2) + +modifyEnv need_to_modify zapper env1 env2 env + | need_to_modify = foldr zap env (keysUFM (env1 `minusUFM` env2)) + | otherwise = env + where + zap uniq env = addToUFM_Directly env uniq (zapper current_val) + where + current_val = expectJust "modifyEnv" (lookupUFM_Directly env uniq) +\end{code} + + +%************************************************************************ +%* * +\subsection{LUB and BOTH} +%* * +%************************************************************************ + +\begin{code} +lub :: Demand -> Demand -> Demand + +lub Bot d2 = d2 +lub Abs d2 = absLub d2 +lub Top d2 = Top +lub (Defer ds1) d2 = defer (Eval ds1 `lub` d2) + +lub (Call d1) (Call d2) = Call (d1 `lub` d2) +lub d1@(Call _) (Box d2) = d1 `lub` d2 -- Just strip the box +lub d1@(Call _) d2@(Eval _) = d2 -- Presumably seq or vanilla eval +lub d1@(Call _) d2 = d2 `lub` d1 -- Bot, Abs, Top + +-- For the Eval case, we use these approximation rules +-- Box Bot <= Eval (Box Bot ...) +-- Box Top <= Defer (Box Bot ...) +-- Box (Eval ds) <= Eval (map Box ds) +lub (Eval ds1) (Eval ds2) = Eval (ds1 `lubs` ds2) +lub (Eval ds1) (Box Bot) = Eval (mapDmds (`lub` Box Bot) ds1) +lub (Eval ds1) (Box (Eval ds2)) = Eval (ds1 `lubs` mapDmds box ds2) +lub (Eval ds1) (Box Abs) = deferEval (mapDmds (`lub` Box Bot) ds1) +lub d1@(Eval _) d2 = d2 `lub` d1 -- Bot,Abs,Top,Call,Defer + +lub (Box d1) (Box d2) = box (d1 `lub` d2) +lub d1@(Box _) d2 = d2 `lub` d1 + +lubs = zipWithDmds lub + +--------------------- +-- box is the smart constructor for Box +-- It computes <B,bot> & d +-- INVARIANT: (Box d) => d = Bot, Abs, Eval +-- Seems to be no point in allowing (Box (Call d)) +box (Call d) = Call d -- The odd man out. Why? +box (Box d) = Box d +box (Defer _) = lazyDmd +box Top = lazyDmd -- Box Abs and Box Top +box Abs = lazyDmd -- are the same <B,L> +box d = Box d -- Bot, Eval + +--------------- +defer :: Demand -> Demand + +-- defer is the smart constructor for Defer +-- The idea is that (Defer ds) = <U(ds), L> +-- +-- It specifies what happens at a lazy function argument +-- or a lambda; the L* operator +-- Set the strictness part to L, but leave +-- the boxity side unaffected +-- It also ensures that Defer (Eval [LLLL]) = L + +defer Bot = Abs +defer Abs = Abs +defer Top = Top +defer (Call _) = lazyDmd -- Approximation here? +defer (Box _) = lazyDmd +defer (Defer ds) = Defer ds +defer (Eval ds) = deferEval ds + +-- deferEval ds = defer (Eval ds) +deferEval ds | allTop ds = Top + | otherwise = Defer ds + +--------------------- +absLub :: Demand -> Demand +-- Computes (Abs `lub` d) +-- For the Bot case consider +-- f x y = if ... then x else error x +-- Then for y we get Abs `lub` Bot, and we really +-- want Abs overall +absLub Bot = Abs +absLub Abs = Abs +absLub Top = Top +absLub (Call _) = Top +absLub (Box _) = Top +absLub (Eval ds) = Defer (absLubs ds) -- Or (Defer ds)? +absLub (Defer ds) = Defer (absLubs ds) -- Or (Defer ds)? + +absLubs = mapDmds absLub + +--------------- +both :: Demand -> Demand -> Demand + +both Abs d2 = d2 + +both Bot Bot = Bot +both Bot Abs = Bot +both Bot (Eval ds) = Eval (mapDmds (`both` Bot) ds) + -- Consider + -- f x = error x + -- From 'error' itself we get demand Bot on x + -- From the arg demand on x we get + -- x :-> evalDmd = Box (Eval (Poly Abs)) + -- So we get Bot `both` Box (Eval (Poly Abs)) + -- = Seq Keep (Poly Bot) + -- + -- Consider also + -- f x = if ... then error (fst x) else fst x + -- Then we get (Eval (Box Bot, Bot) `lub` Eval (SA)) + -- = Eval (SA) + -- which is what we want. +both Bot d = errDmd + +both Top Bot = errDmd +both Top Abs = Top +both Top Top = Top +both Top (Box d) = Box d +both Top (Call d) = Call d +both Top (Eval ds) = Eval (mapDmds (`both` Top) ds) +both Top (Defer ds) -- = defer (Top `both` Eval ds) + -- = defer (Eval (mapDmds (`both` Top) ds)) + = deferEval (mapDmds (`both` Top) ds) + + +both (Box d1) (Box d2) = box (d1 `both` d2) +both (Box d1) d2@(Call _) = box (d1 `both` d2) +both (Box d1) d2@(Eval _) = box (d1 `both` d2) +both (Box d1) (Defer d2) = Box d1 +both d1@(Box _) d2 = d2 `both` d1 + +both (Call d1) (Call d2) = Call (d1 `both` d2) +both (Call d1) (Eval ds2) = Call d1 -- Could do better for (Poly Bot)? +both (Call d1) (Defer ds2) = Call d1 -- Ditto +both d1@(Call _) d2 = d1 `both` d1 + +both (Eval ds1) (Eval ds2) = Eval (ds1 `boths` ds2) +both (Eval ds1) (Defer ds2) = Eval (ds1 `boths` mapDmds defer ds2) +both d1@(Eval ds1) d2 = d2 `both` d1 + +both (Defer ds1) (Defer ds2) = deferEval (ds1 `boths` ds2) +both d1@(Defer ds1) d2 = d2 `both` d1 + +boths = zipWithDmds both +\end{code} + + + +%************************************************************************ +%* * +\subsection{Miscellaneous +%* * +%************************************************************************ + + +\begin{code} +#ifdef OLD_STRICTNESS +get_changes binds = vcat (map get_changes_bind binds) + +get_changes_bind (Rec pairs) = vcat (map get_changes_pr pairs) +get_changes_bind (NonRec id rhs) = get_changes_pr (id,rhs) + +get_changes_pr (id,rhs) + = get_changes_var id $$ get_changes_expr rhs + +get_changes_var var + | isId var = get_changes_str var $$ get_changes_dmd var + | otherwise = empty + +get_changes_expr (Type t) = empty +get_changes_expr (Var v) = empty +get_changes_expr (Lit l) = empty +get_changes_expr (Note n e) = get_changes_expr e +get_changes_expr (App e1 e2) = get_changes_expr e1 $$ get_changes_expr e2 +get_changes_expr (Lam b e) = {- get_changes_var b $$ -} get_changes_expr e +get_changes_expr (Let b e) = get_changes_bind b $$ get_changes_expr e +get_changes_expr (Case e b a) = get_changes_expr e $$ {- get_changes_var b $$ -} vcat (map get_changes_alt a) + +get_changes_alt (con,bs,rhs) = {- vcat (map get_changes_var bs) $$ -} get_changes_expr rhs + +get_changes_str id + | new_better && old_better = empty + | new_better = message "BETTER" + | old_better = message "WORSE" + | otherwise = message "INCOMPARABLE" + where + message word = text word <+> text "strictness for" <+> ppr id <+> info + info = (text "Old" <+> ppr old) $$ (text "New" <+> ppr new) + new = squashSig (idNewStrictness id) -- Don't report spurious diffs that the old + -- strictness analyser can't track + old = newStrictnessFromOld (idName id) (idArity id) (idStrictness id) (idCprInfo id) + old_better = old `betterStrictness` new + new_better = new `betterStrictness` old + +get_changes_dmd id + | isUnLiftedType (idType id) = empty -- Not useful + | new_better && old_better = empty + | new_better = message "BETTER" + | old_better = message "WORSE" + | otherwise = message "INCOMPARABLE" + where + message word = text word <+> text "demand for" <+> ppr id <+> info + info = (text "Old" <+> ppr old) $$ (text "New" <+> ppr new) + new = squashDmd (argDemand (idNewDemandInfo id)) -- To avoid spurious improvements + -- A bit of a hack + old = newDemand (idDemandInfo id) + new_better = new `betterDemand` old + old_better = old `betterDemand` new + +betterStrictness :: StrictSig -> StrictSig -> Bool +betterStrictness (StrictSig t1) (StrictSig t2) = betterDmdType t1 t2 + +betterDmdType t1 t2 = (t1 `lubType` t2) == t2 + +betterDemand :: Demand -> Demand -> Bool +-- If d1 `better` d2, and d2 `better` d2, then d1==d2 +betterDemand d1 d2 = (d1 `lub` d2) == d2 + +squashSig (StrictSig (DmdType fv ds res)) + = StrictSig (DmdType emptyDmdEnv (map squashDmd ds) res) + where + -- squash just gets rid of call demands + -- which the old analyser doesn't track +squashDmd (Call d) = evalDmd +squashDmd (Box d) = Box (squashDmd d) +squashDmd (Eval ds) = Eval (mapDmds squashDmd ds) +squashDmd (Defer ds) = Defer (mapDmds squashDmd ds) +squashDmd d = d +#endif +\end{code} diff --git a/compiler/stranal/SaAbsInt.lhs b/compiler/stranal/SaAbsInt.lhs new file mode 100644 index 0000000000..a6a79ec166 --- /dev/null +++ b/compiler/stranal/SaAbsInt.lhs @@ -0,0 +1,925 @@ +% +% (c) The GRASP/AQUA Project, Glasgow University, 1993-1998 +% +\section[SaAbsInt]{Abstract interpreter for strictness analysis} + +\begin{code} +#ifndef OLD_STRICTNESS +-- If OLD_STRICTNESS is off, omit all exports +module SaAbsInt () where + +#else +module SaAbsInt ( + findStrictness, + findDemand, findDemandAlts, + absEval, + widen, + fixpoint, + isBot + ) where + +#include "HsVersions.h" + +import StaticFlags ( opt_AllStrict, opt_NumbersStrict ) +import CoreSyn +import CoreUnfold ( maybeUnfoldingTemplate ) +import Id ( Id, idType, idUnfolding, isDataConWorkId_maybe, + idStrictness, + ) +import DataCon ( dataConTyCon, splitProductType_maybe, dataConRepArgTys ) +import IdInfo ( StrictnessInfo(..) ) +import Demand ( Demand(..), wwPrim, wwStrict, wwUnpack, wwLazy, + mkStrictnessInfo, isLazy + ) +import SaLib +import TyCon ( isProductTyCon, isRecursiveTyCon ) +import Type ( splitTyConApp_maybe, + isUnLiftedType, Type ) +import TyCon ( tyConUnique ) +import PrelInfo ( numericTyKeys ) +import Util ( isIn, nOfThem, zipWithEqual, equalLength ) +import Outputable +\end{code} + +%************************************************************************ +%* * +\subsection[AbsVal-ops]{Operations on @AbsVals@} +%* * +%************************************************************************ + +Least upper bound, greatest lower bound. + +\begin{code} +lub, glb :: AbsVal -> AbsVal -> AbsVal + +lub AbsBot val2 = val2 +lub val1 AbsBot = val1 + +lub (AbsProd xs) (AbsProd ys) = AbsProd (zipWithEqual "lub" lub xs ys) + +lub _ _ = AbsTop -- Crude, but conservative + -- The crudity only shows up if there + -- are functions involved + +-- Slightly funny glb; for absence analysis only; +-- AbsBot is the safe answer. +-- +-- Using anyBot rather than just testing for AbsBot is important. +-- Consider: +-- +-- f = \a b -> ... +-- +-- g = \x y z -> case x of +-- [] -> f x +-- (p:ps) -> f p +-- +-- Now, the abstract value of the branches of the case will be an +-- AbsFun, but when testing for z's absence we want to spot that it's +-- an AbsFun which can't possibly return AbsBot. So when glb'ing we +-- mustn't be too keen to bale out and return AbsBot; the anyBot test +-- spots that (f x) can't possibly return AbsBot. + +-- We have also tripped over the following interesting case: +-- case x of +-- [] -> \y -> 1 +-- (p:ps) -> f +-- +-- Now, suppose f is bound to AbsTop. Does this expression mention z? +-- Obviously not. But the case will take the glb of AbsTop (for f) and +-- an AbsFun (for \y->1). We should not bale out and give AbsBot, because +-- that would say that it *does* mention z (or anything else for that matter). +-- Nor can we always return AbsTop, because the AbsFun might be something +-- like (\y->z), which obviously does mention z. The point is that we're +-- glbing two functions, and AbsTop is not actually the top of the function +-- lattice. It is more like (\xyz -> x|y|z); that is, AbsTop returns +-- poison iff any of its arguments do. + +-- Deal with functions specially, because AbsTop isn't the +-- top of their domain. + +glb v1 v2 + | is_fun v1 || is_fun v2 + = if not (anyBot v1) && not (anyBot v2) + then + AbsTop + else + AbsBot + where + is_fun (AbsFun _ _) = True + is_fun (AbsApproxFun _ _) = True -- Not used, but the glb works ok + is_fun other = False + +-- The non-functional cases are quite straightforward + +glb (AbsProd xs) (AbsProd ys) = AbsProd (zipWithEqual "glb" glb xs ys) + +glb AbsTop v2 = v2 +glb v1 AbsTop = v1 + +glb _ _ = AbsBot -- Be pessimistic +\end{code} + +@isBot@ returns True if its argument is (a representation of) bottom. The +``representation'' part is because we need to detect the bottom {\em function} +too. To detect the bottom function, bind its args to top, and see if it +returns bottom. + +Used only in strictness analysis: +\begin{code} +isBot :: AbsVal -> Bool + +isBot AbsBot = True +isBot other = False -- Functions aren't bottom any more +\end{code} + +Used only in absence analysis: + +\begin{code} +anyBot :: AbsVal -> Bool + +anyBot AbsBot = True -- poisoned! +anyBot AbsTop = False +anyBot (AbsProd vals) = any anyBot vals +anyBot (AbsFun bndr_ty abs_fn) = anyBot (abs_fn AbsTop) +anyBot (AbsApproxFun _ val) = anyBot val +\end{code} + +@widen@ takes an @AbsVal@, $val$, and returns and @AbsVal@ which is +approximated by $val$. Furthermore, the result has no @AbsFun@s in +it, so it can be compared for equality by @sameVal@. + +\begin{code} +widen :: AnalysisKind -> AbsVal -> AbsVal + +-- Widening is complicated by the fact that funtions are lifted +widen StrAnal the_fn@(AbsFun bndr_ty _) + = case widened_body of + AbsApproxFun ds val -> AbsApproxFun (d : ds) val + where + d = findRecDemand str_fn abs_fn bndr_ty + str_fn val = isBot (foldl (absApply StrAnal) the_fn + (val : [AbsTop | d <- ds])) + + other -> AbsApproxFun [d] widened_body + where + d = findRecDemand str_fn abs_fn bndr_ty + str_fn val = isBot (absApply StrAnal the_fn val) + where + widened_body = widen StrAnal (absApply StrAnal the_fn AbsTop) + abs_fn val = False -- Always says poison; so it looks as if + -- nothing is absent; safe + +{- OLD comment... + This stuff is now instead handled neatly by the fact that AbsApproxFun + contains an AbsVal inside it. SLPJ Jan 97 + + | isBot abs_body = AbsBot + -- It's worth checking for a function which is unconditionally + -- bottom. Consider + -- + -- f x y = let g y = case x of ... + -- in (g ..) + (g ..) + -- + -- Here, when we are considering strictness of f in x, we'll + -- evaluate the body of f with x bound to bottom. The current + -- strategy is to bind g to its *widened* value; without the isBot + -- (...) test above, we'd bind g to an AbsApproxFun, and deliver + -- Top, not Bot as the value of f's rhs. The test spots the + -- unconditional bottom-ness of g when x is bottom. (Another + -- alternative here would be to bind g to its exact abstract + -- value, but that entails lots of potential re-computation, at + -- every application of g.) +-} + +widen StrAnal (AbsProd vals) = AbsProd (map (widen StrAnal) vals) +widen StrAnal other_val = other_val + + +widen AbsAnal the_fn@(AbsFun bndr_ty _) + | anyBot widened_body = AbsBot + -- In the absence-analysis case it's *essential* to check + -- that the function has no poison in its body. If it does, + -- anywhere, then the whole function is poisonous. + + | otherwise + = case widened_body of + AbsApproxFun ds val -> AbsApproxFun (d : ds) val + where + d = findRecDemand str_fn abs_fn bndr_ty + abs_fn val = not (anyBot (foldl (absApply AbsAnal) the_fn + (val : [AbsTop | d <- ds]))) + + other -> AbsApproxFun [d] widened_body + where + d = findRecDemand str_fn abs_fn bndr_ty + abs_fn val = not (anyBot (absApply AbsAnal the_fn val)) + where + widened_body = widen AbsAnal (absApply AbsAnal the_fn AbsTop) + str_fn val = True -- Always says non-termination; + -- that'll make findRecDemand peer into the + -- structure of the value. + +widen AbsAnal (AbsProd vals) = AbsProd (map (widen AbsAnal) vals) + + -- It's desirable to do a good job of widening for product + -- values. Consider + -- + -- let p = (x,y) + -- in ...(case p of (x,y) -> x)... + -- + -- Now, is y absent in this expression? Currently the + -- analyser widens p before looking at p's scope, to avoid + -- lots of recomputation in the case where p is a function. + -- So if widening doesn't have a case for products, we'll + -- widen p to AbsBot (since when searching for absence in y we + -- bind y to poison ie AbsBot), and now we are lost. + +widen AbsAnal other_val = other_val + +-- WAS: if anyBot val then AbsBot else AbsTop +-- Nowadays widen is doing a better job on functions for absence analysis. +\end{code} + +@crudeAbsWiden@ is used just for absence analysis, and always +returns AbsTop or AbsBot, so it widens to a two-point domain + +\begin{code} +crudeAbsWiden :: AbsVal -> AbsVal +crudeAbsWiden val = if anyBot val then AbsBot else AbsTop +\end{code} + +@sameVal@ compares two abstract values for equality. It can't deal with +@AbsFun@, but that should have been removed earlier in the day by @widen@. + +\begin{code} +sameVal :: AbsVal -> AbsVal -> Bool -- Can't handle AbsFun! + +#ifdef DEBUG +sameVal (AbsFun _ _) _ = panic "sameVal: AbsFun: arg1" +sameVal _ (AbsFun _ _) = panic "sameVal: AbsFun: arg2" +#endif + +sameVal AbsBot AbsBot = True +sameVal AbsBot other = False -- widen has reduced AbsFun bots to AbsBot + +sameVal AbsTop AbsTop = True +sameVal AbsTop other = False -- Right? + +sameVal (AbsProd vals1) (AbsProd vals2) = and (zipWithEqual "sameVal" sameVal vals1 vals2) +sameVal (AbsProd _) AbsTop = False +sameVal (AbsProd _) AbsBot = False + +sameVal (AbsApproxFun str1 v1) (AbsApproxFun str2 v2) = str1 == str2 && sameVal v1 v2 +sameVal (AbsApproxFun _ _) AbsTop = False +sameVal (AbsApproxFun _ _) AbsBot = False + +sameVal val1 val2 = panic "sameVal: type mismatch or AbsFun encountered" +\end{code} + + +@evalStrictness@ compares a @Demand@ with an abstract value, returning +@True@ iff the abstract value is {\em less defined} than the demand. +(@True@ is the exciting answer; @False@ is always safe.) + +\begin{code} +evalStrictness :: Demand + -> AbsVal + -> Bool -- True iff the value is sure + -- to be less defined than the Demand + +evalStrictness (WwLazy _) _ = False +evalStrictness WwStrict val = isBot val +evalStrictness WwEnum val = isBot val + +evalStrictness (WwUnpack _ demand_info) val + = case val of + AbsTop -> False + AbsBot -> True + AbsProd vals + | not (equalLength vals demand_info) -> pprTrace "TELL SIMON: evalStrictness" (ppr demand_info $$ ppr val) + False + | otherwise -> or (zipWithEqual "evalStrictness" evalStrictness demand_info vals) + + _ -> pprTrace "evalStrictness?" empty False + +evalStrictness WwPrim val + = case val of + AbsTop -> False + AbsBot -> True -- Can happen: consider f (g x), where g is a + -- recursive function returning an Int# that diverges + + other -> pprPanic "evalStrictness: WwPrim:" (ppr other) +\end{code} + +For absence analysis, we're interested in whether "poison" in the +argument (ie a bottom therein) can propagate to the result of the +function call; that is, whether the specified demand can {\em +possibly} hit poison. + +\begin{code} +evalAbsence (WwLazy True) _ = False -- Can't possibly hit poison + -- with Absent demand + +evalAbsence (WwUnpack _ demand_info) val + = case val of + AbsTop -> False -- No poison in here + AbsBot -> True -- Pure poison + AbsProd vals + | not (equalLength vals demand_info) -> pprTrace "TELL SIMON: evalAbsence" (ppr demand_info $$ ppr val) + True + | otherwise -> or (zipWithEqual "evalAbsence" evalAbsence demand_info vals) + _ -> pprTrace "TELL SIMON: evalAbsence" + (ppr demand_info $$ ppr val) + True + +evalAbsence other val = anyBot val + -- The demand is conservative; even "Lazy" *might* evaluate the + -- argument arbitrarily so we have to look everywhere for poison +\end{code} + +%************************************************************************ +%* * +\subsection[absEval]{Evaluate an expression in the abstract domain} +%* * +%************************************************************************ + +\begin{code} +-- The isBottomingId stuf is now dealt with via the Id's strictness info +-- absId anal var env | isBottomingId var +-- = case anal of +-- StrAnal -> AbsBot -- See discussion below +-- AbsAnal -> AbsTop -- Just want to see if there's any poison in + -- error's arg + +absId anal var env + = case (lookupAbsValEnv env var, + isDataConWorkId_maybe var, + idStrictness var, + maybeUnfoldingTemplate (idUnfolding var)) of + + (Just abs_val, _, _, _) -> + abs_val -- Bound in the environment + + (_, Just data_con, _, _) | isProductTyCon tycon && + not (isRecursiveTyCon tycon) + -> -- A product. We get infinite loops if we don't + -- check for recursive products! + -- The strictness info on the constructor + -- isn't expressive enough to contain its abstract value + productAbsVal (dataConRepArgTys data_con) [] + where + tycon = dataConTyCon data_con + + (_, _, NoStrictnessInfo, Just unfolding) -> + -- We have an unfolding for the expr + -- Assume the unfolding has no free variables since it + -- came from inside the Id + absEval anal unfolding env + -- Notice here that we only look in the unfolding if we don't + -- have strictness info (an unusual situation). + -- We could have chosen to look in the unfolding if it exists, + -- and only try the strictness info if it doesn't, and that would + -- give more accurate results, at the cost of re-abstract-interpreting + -- the unfolding every time. + -- We found only one place where the look-at-unfolding-first + -- method gave better results, which is in the definition of + -- showInt in the Prelude. In its defintion, fromIntegral is + -- not inlined (it's big) but ab-interp-ing its unfolding gave + -- a better result than looking at its strictness only. + -- showInt :: Integral a => a -> [Char] -> [Char] + -- ! {-# GHC_PRAGMA _A_ 1 _U_ 122 _S_ + -- "U(U(U(U(SA)AAAAAAAAL)AA)AAAAASAAASA)" {...} _N_ _N_ #-} + -- --- 42,44 ---- + -- showInt :: Integral a => a -> [Char] -> [Char] + -- ! {-# GHC_PRAGMA _A_ 1 _U_ 122 _S_ + -- "U(U(U(U(SL)LLLLLLLLL)LL)LLLLLSLLLLL)" _N_ _N_ #-} + + + (_, _, strictness_info, _) -> + -- Includes NoUnfolding + -- Try the strictness info + absValFromStrictness anal strictness_info + +productAbsVal [] rev_abs_args = AbsProd (reverse rev_abs_args) +productAbsVal (arg_ty : arg_tys) rev_abs_args = AbsFun arg_ty (\ abs_arg -> productAbsVal arg_tys (abs_arg : rev_abs_args)) +\end{code} + +\begin{code} +absEval :: AnalysisKind -> CoreExpr -> AbsValEnv -> AbsVal + +absEval anal (Type ty) env = AbsTop +absEval anal (Var var) env = absId anal var env +\end{code} + +Discussion about error (following/quoting Lennart): Any expression +'error e' is regarded as bottom (with HBC, with the -ffail-strict +flag, on with -O). + +Regarding it as bottom gives much better strictness properties for +some functions. E.g. + + f [x] y = x+y + f (x:xs) y = f xs (x+y) +i.e. + f [] _ = error "no match" + f [x] y = x+y + f (x:xs) y = f xs (x+y) + +is strict in y, which you really want. But, it may lead to +transformations that turn a call to \tr{error} into non-termination. +(The odds of this happening aren't good.) + +Things are a little different for absence analysis, because we want +to make sure that any poison (?????) + +\begin{code} +absEval anal (Lit _) env = AbsTop + -- Literals terminate (strictness) and are not poison (absence) +\end{code} + +\begin{code} +absEval anal (Lam bndr body) env + | isTyVar bndr = absEval anal body env -- Type lambda + | otherwise = AbsFun (idType bndr) abs_fn -- Value lambda + where + abs_fn arg = absEval anal body (addOneToAbsValEnv env bndr arg) + +absEval anal (App expr (Type ty)) env + = absEval anal expr env -- Type appplication +absEval anal (App f val_arg) env + = absApply anal (absEval anal f env) -- Value applicationn + (absEval anal val_arg env) +\end{code} + +\begin{code} +absEval anal expr@(Case scrut case_bndr alts) env + = let + scrut_val = absEval anal scrut env + alts_env = addOneToAbsValEnv env case_bndr scrut_val + in + case (scrut_val, alts) of + (AbsBot, _) -> AbsBot + + (AbsProd arg_vals, [(con, bndrs, rhs)]) + | con /= DEFAULT -> + -- The scrutinee is a product value, so it must be of a single-constr + -- type; so the constructor in this alternative must be the right one + -- so we can go ahead and bind the constructor args to the components + -- of the product value. + ASSERT(equalLength arg_vals val_bndrs) + absEval anal rhs rhs_env + where + val_bndrs = filter isId bndrs + rhs_env = growAbsValEnvList alts_env (val_bndrs `zip` arg_vals) + + other -> absEvalAlts anal alts alts_env +\end{code} + +For @Lets@ we widen the value we get. This is nothing to +do with fixpointing. The reason is so that we don't get an explosion +in the amount of computation. For example, consider: +\begin{verbatim} + let + g a = case a of + q1 -> ... + q2 -> ... + f x = case x of + p1 -> ...g r... + p2 -> ...g s... + in + f e +\end{verbatim} +If we bind @f@ and @g@ to their exact abstract value, then we'll +``execute'' one call to @f@ and {\em two} calls to @g@. This can blow +up exponentially. Widening cuts it off by making a fixed +approximation to @f@ and @g@, so that the bodies of @f@ and @g@ are +not evaluated again at all when they are called. + +Of course, this can lose useful joint strictness, which is sad. An +alternative approach would be to try with a certain amount of ``fuel'' +and be prepared to bale out. + +\begin{code} +absEval anal (Let (NonRec binder e1) e2) env + = let + new_env = addOneToAbsValEnv env binder (widen anal (absEval anal e1 env)) + in + -- The binder of a NonRec should *not* be of unboxed type, + -- hence no need to strictly evaluate the Rhs. + absEval anal e2 new_env + +absEval anal (Let (Rec pairs) body) env + = let + (binders,rhss) = unzip pairs + rhs_vals = cheapFixpoint anal binders rhss env -- Returns widened values + new_env = growAbsValEnvList env (binders `zip` rhs_vals) + in + absEval anal body new_env + +absEval anal (Note (Coerce _ _) expr) env = AbsTop + -- Don't look inside coerces, becuase they + -- are usually recursive newtypes + -- (Could improve, for the error case, but we're about + -- to kill this analyser anyway.) +absEval anal (Note note expr) env = absEval anal expr env +\end{code} + +\begin{code} +absEvalAlts :: AnalysisKind -> [CoreAlt] -> AbsValEnv -> AbsVal +absEvalAlts anal alts env + = combine anal (map go alts) + where + combine StrAnal = foldr1 lub -- Diverge only if all diverge + combine AbsAnal = foldr1 glb -- Find any poison + + go (con, bndrs, rhs) + = absEval anal rhs rhs_env + where + rhs_env = growAbsValEnvList env (filter isId bndrs `zip` repeat AbsTop) +\end{code} + +%************************************************************************ +%* * +\subsection[absApply]{Apply an abstract function to an abstract argument} +%* * +%************************************************************************ + +Easy ones first: + +\begin{code} +absApply :: AnalysisKind -> AbsVal -> AbsVal -> AbsVal + +absApply anal AbsBot arg = AbsBot + -- AbsBot represents the abstract bottom *function* too + +absApply StrAnal AbsTop arg = AbsTop +absApply AbsAnal AbsTop arg = if anyBot arg + then AbsBot + else AbsTop + -- To be conservative, we have to assume that a function about + -- which we know nothing (AbsTop) might look at some part of + -- its argument +\end{code} + +An @AbsFun@ with only one more argument needed---bind it and eval the +result. A @Lam@ with two or more args: return another @AbsFun@ with +an augmented environment. + +\begin{code} +absApply anal (AbsFun bndr_ty abs_fn) arg = abs_fn arg +\end{code} + +\begin{code} +absApply StrAnal (AbsApproxFun (d:ds) val) arg + = case ds of + [] -> val' + other -> AbsApproxFun ds val' -- Result is non-bot if there are still args + where + val' | evalStrictness d arg = AbsBot + | otherwise = val + +absApply AbsAnal (AbsApproxFun (d:ds) val) arg + = if evalAbsence d arg + then AbsBot -- Poison in arg means poison in the application + else case ds of + [] -> val + other -> AbsApproxFun ds val + +#ifdef DEBUG +absApply anal f@(AbsProd _) arg + = pprPanic ("absApply: Duff function: AbsProd." ++ show anal) ((ppr f) <+> (ppr arg)) +#endif +\end{code} + + + + +%************************************************************************ +%* * +\subsection[findStrictness]{Determine some binders' strictness} +%* * +%************************************************************************ + +\begin{code} +findStrictness :: Id + -> AbsVal -- Abstract strictness value of function + -> AbsVal -- Abstract absence value of function + -> StrictnessInfo -- Resulting strictness annotation + +findStrictness id (AbsApproxFun str_ds str_res) (AbsApproxFun abs_ds _) + -- You might think there's really no point in describing detailed + -- strictness for a divergent function; + -- If it's fully applied we get bottom regardless of the + -- argument. If it's not fully applied we don't get bottom. + -- Finally, we don't want to regard the args of a divergent function + -- as 'interesting' for inlining purposes (see Simplify.prepareArgs) + -- + -- HOWEVER, if we make diverging functions appear lazy, they + -- don't get wrappers, and then we get dreadful reboxing. + -- See notes with WwLib.worthSplitting + = find_strictness id str_ds str_res abs_ds + +findStrictness id str_val abs_val + | isBot str_val = mkStrictnessInfo ([], True) + | otherwise = NoStrictnessInfo + +-- The list of absence demands passed to combineDemands +-- can be shorter than the list of absence demands +-- +-- lookup = \ dEq -> letrec { +-- lookup = \ key ds -> ...lookup... +-- } +-- in lookup +-- Here the strictness value takes three args, but the absence value +-- takes only one, for reasons I don't quite understand (see cheapFixpoint) + +find_strictness id orig_str_ds orig_str_res orig_abs_ds + = mkStrictnessInfo (go orig_str_ds orig_abs_ds, res_bot) + where + res_bot = isBot orig_str_res + + go str_ds abs_ds = zipWith mk_dmd str_ds (abs_ds ++ repeat wwLazy) + + mk_dmd str_dmd (WwLazy True) + = WARN( not (res_bot || isLazy str_dmd), + ppr id <+> ppr orig_str_ds <+> ppr orig_abs_ds ) + -- If the arg isn't used we jolly well don't expect the function + -- to be strict in it. Unless the function diverges. + WwLazy True -- Best of all + + mk_dmd (WwUnpack u str_ds) + (WwUnpack _ abs_ds) = WwUnpack u (go str_ds abs_ds) + + mk_dmd str_dmd abs_dmd = str_dmd +\end{code} + + +\begin{code} +findDemand dmd str_env abs_env expr binder + = findRecDemand str_fn abs_fn (idType binder) + where + str_fn val = evalStrictness dmd (absEval StrAnal expr (addOneToAbsValEnv str_env binder val)) + abs_fn val = not (evalAbsence dmd (absEval AbsAnal expr (addOneToAbsValEnv abs_env binder val))) + +findDemandAlts dmd str_env abs_env alts binder + = findRecDemand str_fn abs_fn (idType binder) + where + str_fn val = evalStrictness dmd (absEvalAlts StrAnal alts (addOneToAbsValEnv str_env binder val)) + abs_fn val = not (evalAbsence dmd (absEvalAlts AbsAnal alts (addOneToAbsValEnv abs_env binder val))) +\end{code} + +@findRecDemand@ is where we finally convert strictness/absence info +into ``Demands'' which we can pin on Ids (etc.). + +NOTE: What do we do if something is {\em both} strict and absent? +Should \tr{f x y z = error "foo"} says that \tr{f}'s arguments are all +strict (because of bottoming effect of \tr{error}) or all absent +(because they're not used)? + +Well, for practical reasons, we prefer absence over strictness. In +particular, it makes the ``default defaults'' for class methods (the +ones that say \tr{defm.foo dict = error "I don't exist"}) come out +nicely [saying ``the dict isn't used''], rather than saying it is +strict in every component of the dictionary [massive gratuitious +casing to take the dict apart]. + +But you could have examples where going for strictness would be better +than absence. Consider: +\begin{verbatim} + let x = something big + in + f x y z + g x +\end{verbatim} + +If \tr{x} is marked absent in \tr{f}, but not strict, and \tr{g} is +lazy, then the thunk for \tr{x} will be built. If \tr{f} was strict, +then we'd let-to-case it: +\begin{verbatim} + case something big of + x -> f x y z + g x +\end{verbatim} +Ho hum. + +\begin{code} +findRecDemand :: (AbsVal -> Bool) -- True => function applied to this value yields Bot + -> (AbsVal -> Bool) -- True => function applied to this value yields no poison + -> Type -- The type of the argument + -> Demand + +findRecDemand str_fn abs_fn ty + = if isUnLiftedType ty then -- It's a primitive type! + wwPrim + + else if abs_fn AbsBot then -- It's absent + -- We prefer absence over strictness: see NOTE above. + WwLazy True + + else if not (opt_AllStrict || + (opt_NumbersStrict && is_numeric_type ty) || + str_fn AbsBot) then + WwLazy False -- It's not strict and we're not pretending + + else -- It's strict (or we're pretending it is)! + + case splitProductType_maybe ty of + + Nothing -> wwStrict -- Could have a test for wwEnum, but + -- we don't exploit it yet, so don't bother + + Just (tycon,_,data_con,cmpnt_tys) -- Single constructor case + | isRecursiveTyCon tycon -- Recursive data type; don't unpack + -> wwStrict -- (this applies to newtypes too: + -- e.g. data Void = MkVoid Void) + + | null compt_strict_infos -- A nullary data type + -> wwStrict + + | otherwise -- Some other data type + -> wwUnpack compt_strict_infos + + where + prod_len = length cmpnt_tys + compt_strict_infos + = [ findRecDemand + (\ cmpnt_val -> + str_fn (mkMainlyTopProd prod_len i cmpnt_val) + ) + (\ cmpnt_val -> + abs_fn (mkMainlyTopProd prod_len i cmpnt_val) + ) + cmpnt_ty + | (cmpnt_ty, i) <- cmpnt_tys `zip` [1..] ] + + where + is_numeric_type ty + = case (splitTyConApp_maybe ty) of -- NB: duplicates stuff done above + Nothing -> False + Just (tycon, _) -> tyConUnique tycon `is_elem` numericTyKeys + where + is_elem = isIn "is_numeric_type" + + -- mkMainlyTopProd: make an AbsProd that is all AbsTops ("n"-1 of + -- them) except for a given value in the "i"th position. + + mkMainlyTopProd :: Int -> Int -> AbsVal -> AbsVal + + mkMainlyTopProd n i val + = let + befores = nOfThem (i-1) AbsTop + afters = nOfThem (n-i) AbsTop + in + AbsProd (befores ++ (val : afters)) +\end{code} + +%************************************************************************ +%* * +\subsection[fixpoint]{Fixpointer for the strictness analyser} +%* * +%************************************************************************ + +The @fixpoint@ functions take a list of \tr{(binder, expr)} pairs, an +environment, and returns the abstract value of each binder. + +The @cheapFixpoint@ function makes a conservative approximation, +by binding each of the variables to Top in their own right hand sides. +That allows us to make rapid progress, at the cost of a less-than-wonderful +approximation. + +\begin{code} +cheapFixpoint :: AnalysisKind -> [Id] -> [CoreExpr] -> AbsValEnv -> [AbsVal] + +cheapFixpoint AbsAnal [id] [rhs] env + = [crudeAbsWiden (absEval AbsAnal rhs new_env)] + where + new_env = addOneToAbsValEnv env id AbsTop -- Unsafe starting point! + -- In the just-one-binding case, we guarantee to + -- find a fixed point in just one iteration, + -- because we are using only a two-point domain. + -- This improves matters in cases like: + -- + -- f x y = letrec g = ...g... + -- in g x + -- + -- Here, y isn't used at all, but if g is bound to + -- AbsBot we simply get AbsBot as the next + -- iteration too. + +cheapFixpoint anal ids rhss env + = [widen anal (absEval anal rhs new_env) | rhs <- rhss] + -- We do just one iteration, starting from a safe + -- approximation. This won't do a good job in situations + -- like: + -- \x -> letrec f = ...g... + -- g = ...f...x... + -- in + -- ...f... + -- Here, f will end up bound to Top after one iteration, + -- and hence we won't spot the strictness in x. + -- (A second iteration would solve this. ToDo: try the effect of + -- really searching for a fixed point.) + where + new_env = growAbsValEnvList env [(id,safe_val) | id <- ids] + + safe_val + = case anal of -- The safe starting point + StrAnal -> AbsTop + AbsAnal -> AbsBot +\end{code} + +\begin{code} +fixpoint :: AnalysisKind -> [Id] -> [CoreExpr] -> AbsValEnv -> [AbsVal] + +fixpoint anal [] _ env = [] + +fixpoint anal ids rhss env + = fix_loop initial_vals + where + initial_val id + = case anal of -- The (unsafe) starting point + AbsAnal -> AbsTop + StrAnal -> AbsBot + -- At one stage for StrAnal we said: + -- if (returnsRealWorld (idType id)) + -- then AbsTop -- this is a massively horrible hack (SLPJ 95/05) + -- but no one has the foggiest idea what this hack did, + -- and returnsRealWorld was a stub that always returned False + -- So this comment is all that is left of the hack! + + initial_vals = [ initial_val id | id <- ids ] + + fix_loop :: [AbsVal] -> [AbsVal] + + fix_loop current_widened_vals + = let + new_env = growAbsValEnvList env (ids `zip` current_widened_vals) + new_vals = [ absEval anal rhs new_env | rhs <- rhss ] + new_widened_vals = map (widen anal) new_vals + in + if (and (zipWith sameVal current_widened_vals new_widened_vals)) then + current_widened_vals + + -- NB: I was too chicken to make that a zipWithEqual, + -- lest I jump into a black hole. WDP 96/02 + + -- Return the widened values. We might get a slightly + -- better value by returning new_vals (which we used to + -- do, see below), but alas that means that whenever the + -- function is called we have to re-execute it, which is + -- expensive. + + -- OLD VERSION + -- new_vals + -- Return the un-widened values which may be a bit better + -- than the widened ones, and are guaranteed safe, since + -- they are one iteration beyond current_widened_vals, + -- which itself is a fixed point. + else + fix_loop new_widened_vals +\end{code} + +For absence analysis, we make do with a very very simple approach: +look for convergence in a two-point domain. + +We used to use just one iteration, starting with the variables bound +to @AbsBot@, which is safe. + +Prior to that, we used one iteration starting from @AbsTop@ (which +isn't safe). Why isn't @AbsTop@ safe? Consider: +\begin{verbatim} + letrec + x = ...p..d... + d = (x,y) + in + ... +\end{verbatim} +Here, if p is @AbsBot@, then we'd better {\em not} end up with a ``fixed +point'' of @d@ being @(AbsTop, AbsTop)@! An @AbsBot@ initial value is +safe because it gives poison more often than really necessary, and +thus may miss some absence, but will never claim absence when it ain't +so. + +Anyway, one iteration starting with everything bound to @AbsBot@ give +bad results for + + f = \ x -> ...f... + +Here, f would always end up bound to @AbsBot@, which ain't very +clever, because then it would introduce poison whenever it was +applied. Much better to start with f bound to @AbsTop@, and widen it +to @AbsBot@ if any poison shows up. In effect we look for convergence +in the two-point @AbsTop@/@AbsBot@ domain. + +What we miss (compared with the cleverer strictness analysis) is +spotting that in this case + + f = \ x y -> ...y...(f x y')... + +\tr{x} is actually absent, since it is only passed round the loop, never +used. But who cares about missing that? + +NB: despite only having a two-point domain, we may still have many +iterations, because there are several variables involved at once. + +\begin{code} +#endif /* OLD_STRICTNESS */ +\end{code} diff --git a/compiler/stranal/SaLib.lhs b/compiler/stranal/SaLib.lhs new file mode 100644 index 0000000000..338a351530 --- /dev/null +++ b/compiler/stranal/SaLib.lhs @@ -0,0 +1,130 @@ +% +% (c) The GRASP/AQUA Project, Glasgow University, 1993-1998 +% +\section[SaLib]{Basic datatypes, functions for the strictness analyser} + +See also: the ``library'' for the ``back end'' (@SaBackLib@). + +\begin{code} +#ifndef OLD_STRICTNESS +module SaLib () where +#else + +module SaLib ( + AbsVal(..), + AnalysisKind(..), + AbsValEnv{-abstract-}, StrictEnv, AbsenceEnv, + mkAbsApproxFun, + nullAbsValEnv, addOneToAbsValEnv, growAbsValEnvList, + lookupAbsValEnv, + absValFromStrictness + ) where + +#include "HsVersions.h" + +import Type ( Type ) +import VarEnv +import IdInfo ( StrictnessInfo(..) ) +import Demand ( Demand ) +import Outputable +\end{code} + +%************************************************************************ +%* * +\subsection[AbsVal-datatype]{@AbsVal@: abstract values (and @AbsValEnv@)} +%* * +%************************************************************************ + +@AnalysisKind@ tells what kind of analysis is being done. + +\begin{code} +data AnalysisKind + = StrAnal -- We're doing strictness analysis + | AbsAnal -- We're doing absence analysis + deriving Show +\end{code} + +@AbsVal@ is the data type of HNF abstract values. + +\begin{code} +data AbsVal + = AbsTop -- AbsTop is the completely uninformative + -- value + + | AbsBot -- An expression whose abstract value is + -- AbsBot is sure to fail to terminate. + -- AbsBot represents the abstract + -- *function* bottom too. + + | AbsProd [AbsVal] -- (Lifted) product of abstract values + -- "Lifted" means that AbsBot is *different* from + -- AbsProd [AbsBot, ..., AbsBot] + + | AbsFun -- An abstract function, with the given: + Type -- Type of the *argument* to the function + (AbsVal -> AbsVal) -- The function + + | AbsApproxFun -- This is used to represent a coarse + [Demand] -- approximation to a function value. It's an + AbsVal -- abstract function which is strict in its + -- arguments if the Demand so indicates. + -- INVARIANT: the [Demand] is non-empty + + -- AbsApproxFun has to take a *list* of demands, no just one, + -- because function spaces are now lifted. Hence, (f bot top) + -- might be bot, but the partial application (f bot) is a *function*, + -- not bot. + +mkAbsApproxFun :: Demand -> AbsVal -> AbsVal +mkAbsApproxFun d (AbsApproxFun ds val) = AbsApproxFun (d:ds) val +mkAbsApproxFun d val = AbsApproxFun [d] val + +instance Outputable AbsVal where + ppr AbsTop = ptext SLIT("AbsTop") + ppr AbsBot = ptext SLIT("AbsBot") + ppr (AbsProd prod) = hsep [ptext SLIT("AbsProd"), ppr prod] + ppr (AbsFun bndr_ty body) = ptext SLIT("AbsFun") + ppr (AbsApproxFun demands val) + = ptext SLIT("AbsApprox") <+> brackets (interpp'SP demands) <+> ppr val +\end{code} + +%----------- + +An @AbsValEnv@ maps @Ids@ to @AbsVals@. Any unbound @Ids@ are +implicitly bound to @AbsTop@, the completely uninformative, +pessimistic value---see @absEval@ of a @Var@. + +\begin{code} +newtype AbsValEnv = AbsValEnv (IdEnv AbsVal) + +type StrictEnv = AbsValEnv -- Environment for strictness analysis +type AbsenceEnv = AbsValEnv -- Environment for absence analysis + +nullAbsValEnv -- this is the one and only way to create AbsValEnvs + = AbsValEnv emptyVarEnv + +addOneToAbsValEnv (AbsValEnv idenv) y z = AbsValEnv (extendVarEnv idenv y z) +growAbsValEnvList (AbsValEnv idenv) ys = AbsValEnv (extendVarEnvList idenv ys) + +lookupAbsValEnv (AbsValEnv idenv) y + = lookupVarEnv idenv y +\end{code} + +\begin{code} +absValFromStrictness :: AnalysisKind -> StrictnessInfo -> AbsVal + +absValFromStrictness anal NoStrictnessInfo = AbsTop +absValFromStrictness anal (StrictnessInfo args_info bot_result) + = case args_info of -- Check the invariant that the arg list on + [] -> res -- AbsApproxFun is non-empty + _ -> AbsApproxFun args_info res + where + res | not bot_result = AbsTop + | otherwise = case anal of + StrAnal -> AbsBot + AbsAnal -> AbsTop +\end{code} + +\begin{code} +#endif /* OLD_STRICTNESS */ +\end{code} diff --git a/compiler/stranal/StrictAnal.lhs b/compiler/stranal/StrictAnal.lhs new file mode 100644 index 0000000000..242a947074 --- /dev/null +++ b/compiler/stranal/StrictAnal.lhs @@ -0,0 +1,494 @@ +% +% (c) The GRASP/AQUA Project, Glasgow University, 1993-1998 +% +\section[StrictAnal]{``Simple'' Mycroft-style strictness analyser} + +The original version(s) of all strictness-analyser code (except the +Semantique analyser) was written by Andy Gill. + +\begin{code} +#ifndef OLD_STRICTNESS +module StrictAnal ( ) where + +#else + +module StrictAnal ( saBinds ) where + +#include "HsVersions.h" + +import DynFlags ( DynFlags, DynFlag(..) ) +import CoreSyn +import Id ( setIdStrictness, setInlinePragma, + idDemandInfo, setIdDemandInfo, isBottomingId, + Id + ) +import CoreLint ( showPass, endPass ) +import ErrUtils ( dumpIfSet_dyn ) +import SaAbsInt +import SaLib +import Demand ( Demand, wwStrict, isStrict, isLazy ) +import Util ( zipWith3Equal, stretchZipWith, compareLength ) +import BasicTypes ( Activation( NeverActive ) ) +import Outputable +import FastTypes +\end{code} + +%************************************************************************ +%* * +\subsection[Thoughts]{Random thoughts} +%* * +%************************************************************************ + +A note about worker-wrappering. If we have + + f :: Int -> Int + f = let v = <expensive> + in \x -> <body> + +and we deduce that f is strict, it is nevertheless NOT safe to worker-wapper to + + f = \x -> case x of Int x# -> fw x# + fw = \x# -> let x = Int x# + in + let v = <expensive> + in <body> + +because this obviously loses laziness, since now <expensive> +is done each time. Alas. + +WATCH OUT! This can mean that something is unboxed only to be +boxed again. For example + + g x y = f x + +Here g is strict, and *will* split into worker-wrapper. A call to +g, with the wrapper inlined will then be + + case arg of Int a# -> gw a# + +Now g calls f, which has no wrapper, so it has to box it. + + gw = \a# -> f (Int a#) + +Alas and alack. + + +%************************************************************************ +%* * +\subsection[iface-StrictAnal]{Interface to the outside world} +%* * +%************************************************************************ + +@saBinds@ decorates bindings with strictness info. A later +worker-wrapper pass can use this info to create wrappers and +strict workers. + +\begin{code} +saBinds :: DynFlags -> [CoreBind] -> IO [CoreBind] +saBinds dflags binds + = do { + showPass dflags "Strictness analysis"; + + -- Mark each binder with its strictness +#ifndef OMIT_STRANAL_STATS + let { (binds_w_strictness, sa_stats) = saTopBinds binds nullSaStats }; + dumpIfSet_dyn dflags Opt_D_dump_simpl_stats "Strictness analysis statistics" + (pp_stats sa_stats); +#else + let { binds_w_strictness = saTopBindsBinds binds }; +#endif + + endPass dflags "Strictness analysis" Opt_D_dump_stranal + binds_w_strictness + } +\end{code} + +%************************************************************************ +%* * +\subsection[saBinds]{Strictness analysis of bindings} +%* * +%************************************************************************ + +[Some of the documentation about types, etc., in \tr{SaLib} may be +helpful for understanding this module.] + +@saTopBinds@ tags each binder in the program with its @Demand@. +That tells how each binder is {\em used}; if @Strict@, then the binder +is sure to be evaluated to HNF; if @NonStrict@ it may or may not be; +if @Absent@, then it certainly is not used. [DATED; ToDo: update] + +(The above info is actually recorded for posterity in each binder's +IdInfo, notably its @DemandInfo@.) + +We proceed by analysing the bindings top-to-bottom, building up an +environment which maps @Id@s to their abstract values (i.e., an +@AbsValEnv@ maps an @Id@ to its @AbsVal@). + +\begin{code} +saTopBinds :: [CoreBind] -> SaM [CoreBind] -- not exported + +saTopBinds binds + = let + starting_abs_env = nullAbsValEnv + in + do_it starting_abs_env starting_abs_env binds + where + do_it _ _ [] = returnSa [] + do_it senv aenv (b:bs) + = saTopBind senv aenv b `thenSa` \ (senv2, aenv2, new_b) -> + do_it senv2 aenv2 bs `thenSa` \ new_bs -> + returnSa (new_b : new_bs) +\end{code} + +@saTopBind@ is only used for the top level. We don't add any demand +info to these ids because we can't work it out. In any case, it +doesn't do us any good to know whether top-level binders are sure to +be used; we can't turn top-level @let@s into @case@s. + +\begin{code} +saTopBind :: StrictEnv -> AbsenceEnv + -> CoreBind + -> SaM (StrictEnv, AbsenceEnv, CoreBind) + +saTopBind str_env abs_env (NonRec binder rhs) + = saExpr minDemand str_env abs_env rhs `thenSa` \ new_rhs -> + let + str_rhs = absEval StrAnal rhs str_env + abs_rhs = absEval AbsAnal rhs abs_env + + widened_str_rhs = widen StrAnal str_rhs + widened_abs_rhs = widen AbsAnal abs_rhs + -- The widening above is done for efficiency reasons. + -- See notes on Let case in SaAbsInt.lhs + + new_binder + = addStrictnessInfoToTopId + widened_str_rhs widened_abs_rhs + binder + + -- Augment environments with a mapping of the + -- binder to its abstract values, computed by absEval + new_str_env = addOneToAbsValEnv str_env binder widened_str_rhs + new_abs_env = addOneToAbsValEnv abs_env binder widened_abs_rhs + in + returnSa (new_str_env, new_abs_env, NonRec new_binder new_rhs) + +saTopBind str_env abs_env (Rec pairs) + = let + (binders,rhss) = unzip pairs + str_rhss = fixpoint StrAnal binders rhss str_env + abs_rhss = fixpoint AbsAnal binders rhss abs_env + -- fixpoint returns widened values + new_str_env = growAbsValEnvList str_env (binders `zip` str_rhss) + new_abs_env = growAbsValEnvList abs_env (binders `zip` abs_rhss) + new_binders = zipWith3Equal "saTopBind" addStrictnessInfoToTopId + str_rhss abs_rhss binders + in + mapSa (saExpr minDemand new_str_env new_abs_env) rhss `thenSa` \ new_rhss -> + let + new_pairs = new_binders `zip` new_rhss + in + returnSa (new_str_env, new_abs_env, Rec new_pairs) + +-- Hack alert! +-- Top level divergent bindings are marked NOINLINE +-- This avoids fruitless inlining of top level error functions +addStrictnessInfoToTopId str_val abs_val bndr + = if isBottomingId new_id then + new_id `setInlinePragma` NeverActive + else + new_id + where + new_id = addStrictnessInfoToId str_val abs_val bndr +\end{code} + +%************************************************************************ +%* * +\subsection[saExpr]{Strictness analysis of an expression} +%* * +%************************************************************************ + +@saExpr@ computes the strictness of an expression within a given +environment. + +\begin{code} +saExpr :: Demand -> StrictEnv -> AbsenceEnv -> CoreExpr -> SaM CoreExpr + -- The demand is the least demand we expect on the + -- expression. WwStrict is the least, because we're only + -- interested in the expression at all if it's being evaluated, + -- but the demand may be more. E.g. + -- f E + -- where f has strictness u(LL), will evaluate E with demand u(LL) + +minDemand = wwStrict +minDemands = repeat minDemand + +-- When we find an application, do the arguments +-- with demands gotten from the function +saApp str_env abs_env (fun, args) + = sequenceSa sa_args `thenSa` \ args' -> + saExpr minDemand str_env abs_env fun `thenSa` \ fun' -> + returnSa (mkApps fun' args') + where + arg_dmds = case fun of + Var var -> case lookupAbsValEnv str_env var of + Just (AbsApproxFun ds _) + | compareLength ds args /= LT + -- 'ds' is at least as long as 'args'. + -> ds ++ minDemands + other -> minDemands + other -> minDemands + + sa_args = stretchZipWith isTypeArg (error "saApp:dmd") + sa_arg args arg_dmds + -- The arg_dmds are for value args only, we need to skip + -- over the type args when pairing up with the demands + -- Hence the stretchZipWith + + sa_arg arg dmd = saExpr dmd' str_env abs_env arg + where + -- Bring arg demand up to minDemand + dmd' | isLazy dmd = minDemand + | otherwise = dmd + +saExpr _ _ _ e@(Var _) = returnSa e +saExpr _ _ _ e@(Lit _) = returnSa e +saExpr _ _ _ e@(Type _) = returnSa e + +saExpr dmd str_env abs_env (Lam bndr body) + = -- Don't bother to set the demand-info on a lambda binder + -- We do that only for let(rec)-bound functions + saExpr minDemand str_env abs_env body `thenSa` \ new_body -> + returnSa (Lam bndr new_body) + +saExpr dmd str_env abs_env e@(App fun arg) + = saApp str_env abs_env (collectArgs e) + +saExpr dmd str_env abs_env (Note note expr) + = saExpr dmd str_env abs_env expr `thenSa` \ new_expr -> + returnSa (Note note new_expr) + +saExpr dmd str_env abs_env (Case expr case_bndr alts) + = saExpr minDemand str_env abs_env expr `thenSa` \ new_expr -> + mapSa sa_alt alts `thenSa` \ new_alts -> + let + new_case_bndr = addDemandInfoToCaseBndr dmd str_env abs_env alts case_bndr + in + returnSa (Case new_expr new_case_bndr new_alts) + where + sa_alt (con, binders, rhs) + = saExpr dmd str_env abs_env rhs `thenSa` \ new_rhs -> + let + new_binders = map add_demand_info binders + add_demand_info bndr | isTyVar bndr = bndr + | otherwise = addDemandInfoToId dmd str_env abs_env rhs bndr + in + tickCases new_binders `thenSa_` -- stats + returnSa (con, new_binders, new_rhs) + +saExpr dmd str_env abs_env (Let (NonRec binder rhs) body) + = -- Analyse the RHS in the environment at hand + let + -- Find the demand on the RHS + rhs_dmd = findDemand dmd str_env abs_env body binder + + -- Bind this binder to the abstract value of the RHS; analyse + -- the body of the `let' in the extended environment. + str_rhs_val = absEval StrAnal rhs str_env + abs_rhs_val = absEval AbsAnal rhs abs_env + + widened_str_rhs = widen StrAnal str_rhs_val + widened_abs_rhs = widen AbsAnal abs_rhs_val + -- The widening above is done for efficiency reasons. + -- See notes on Let case in SaAbsInt.lhs + + new_str_env = addOneToAbsValEnv str_env binder widened_str_rhs + new_abs_env = addOneToAbsValEnv abs_env binder widened_abs_rhs + + -- Now determine the strictness of this binder; use that info + -- to record DemandInfo/StrictnessInfo in the binder. + new_binder = addStrictnessInfoToId + widened_str_rhs widened_abs_rhs + (binder `setIdDemandInfo` rhs_dmd) + in + tickLet new_binder `thenSa_` -- stats + saExpr rhs_dmd str_env abs_env rhs `thenSa` \ new_rhs -> + saExpr dmd new_str_env new_abs_env body `thenSa` \ new_body -> + returnSa (Let (NonRec new_binder new_rhs) new_body) + +saExpr dmd str_env abs_env (Let (Rec pairs) body) + = let + (binders,rhss) = unzip pairs + str_vals = fixpoint StrAnal binders rhss str_env + abs_vals = fixpoint AbsAnal binders rhss abs_env + -- fixpoint returns widened values + new_str_env = growAbsValEnvList str_env (binders `zip` str_vals) + new_abs_env = growAbsValEnvList abs_env (binders `zip` abs_vals) + in + saExpr dmd new_str_env new_abs_env body `thenSa` \ new_body -> + mapSa (saExpr minDemand new_str_env new_abs_env) rhss `thenSa` \ new_rhss -> + let +-- DON'T add demand info in a Rec! +-- a) it's useless: we can't do let-to-case +-- b) it's incorrect. Consider +-- letrec x = ...y... +-- y = ...x... +-- in ...x... +-- When we ask whether y is demanded we'll bind y to bottom and +-- evaluate the body of the letrec. But that will result in our +-- deciding that y is absent, which is plain wrong! +-- It's much easier simply not to do this. + + improved_binders = zipWith3Equal "saExpr" addStrictnessInfoToId + str_vals abs_vals binders + + new_pairs = improved_binders `zip` new_rhss + in + returnSa (Let (Rec new_pairs) new_body) +\end{code} + + +%************************************************************************ +%* * +\subsection[computeInfos]{Add computed info to binders} +%* * +%************************************************************************ + +Important note (Sept 93). @addStrictnessInfoToId@ is used only for +let(rec) bound variables, and is use to attach the strictness (not +demand) info to the binder. We are careful to restrict this +strictness info to the lambda-bound arguments which are actually +visible, at the top level, lest we accidentally lose laziness by +eagerly looking for an "extra" argument. So we "dig for lambdas" in a +rather syntactic way. + +A better idea might be to have some kind of arity analysis to +tell how many args could safely be grabbed. + +\begin{code} +addStrictnessInfoToId + :: AbsVal -- Abstract strictness value + -> AbsVal -- Ditto absence + -> Id -- The id + -> Id -- Augmented with strictness + +addStrictnessInfoToId str_val abs_val binder + = binder `setIdStrictness` findStrictness binder str_val abs_val +\end{code} + +\begin{code} +addDemandInfoToId :: Demand -> StrictEnv -> AbsenceEnv + -> CoreExpr -- The scope of the id + -> Id + -> Id -- Id augmented with Demand info + +addDemandInfoToId dmd str_env abs_env expr binder + = binder `setIdDemandInfo` (findDemand dmd str_env abs_env expr binder) + +addDemandInfoToCaseBndr dmd str_env abs_env alts binder + = binder `setIdDemandInfo` (findDemandAlts dmd str_env abs_env alts binder) +\end{code} + +%************************************************************************ +%* * +\subsection{Monad used herein for stats} +%* * +%************************************************************************ + +\begin{code} +data SaStats + = SaStats FastInt FastInt -- total/marked-demanded lambda-bound + FastInt FastInt -- total/marked-demanded case-bound + FastInt FastInt -- total/marked-demanded let-bound + -- (excl. top-level; excl. letrecs) + +nullSaStats = SaStats (_ILIT 0) (_ILIT 0) (_ILIT 0) (_ILIT 0) (_ILIT 0) (_ILIT 0) + +thenSa :: SaM a -> (a -> SaM b) -> SaM b +thenSa_ :: SaM a -> SaM b -> SaM b +returnSa :: a -> SaM a + +{-# INLINE thenSa #-} +{-# INLINE thenSa_ #-} +{-# INLINE returnSa #-} + +tickLambda :: Id -> SaM () +tickCases :: [CoreBndr] -> SaM () +tickLet :: Id -> SaM () + +#ifndef OMIT_STRANAL_STATS +type SaM a = SaStats -> (a, SaStats) + +thenSa expr cont stats + = case (expr stats) of { (result, stats1) -> + cont result stats1 } + +thenSa_ expr cont stats + = case (expr stats) of { (_, stats1) -> + cont stats1 } + +returnSa x stats = (x, stats) + +tickLambda var (SaStats tlam dlam tc dc tlet dlet) + = case (tick_demanded var (0,0)) of { (totB, demandedB) -> + let tot = iUnbox totB ; demanded = iUnbox demandedB + in + ((), SaStats (tlam +# tot) (dlam +# demanded) tc dc tlet dlet) } + +tickCases vars (SaStats tlam dlam tc dc tlet dlet) + = case (foldr tick_demanded (0,0) vars) of { (totB, demandedB) -> + let tot = iUnbox totB ; demanded = iUnbox demandedB + in + ((), SaStats tlam dlam (tc +# tot) (dc +# demanded) tlet dlet) } + +tickLet var (SaStats tlam dlam tc dc tlet dlet) + = case (tick_demanded var (0,0)) of { (totB, demandedB) -> + let tot = iUnbox totB ; demanded = iUnbox demandedB + in + ((), SaStats tlam dlam tc dc (tlet +# tot) (dlet +# demanded)) } + +tick_demanded var (tot, demanded) + | isTyVar var = (tot, demanded) + | otherwise + = (tot + 1, + if (isStrict (idDemandInfo var)) + then demanded + 1 + else demanded) + +pp_stats (SaStats tlam dlam tc dc tlet dlet) + = hcat [ptext SLIT("Lambda vars: "), int (iBox dlam), char '/', int (iBox tlam), + ptext SLIT("; Case vars: "), int (iBox dc), char '/', int (iBox tc), + ptext SLIT("; Let vars: "), int (iBox dlet), char '/', int (iBox tlet) + ] + +#else /* OMIT_STRANAL_STATS */ +-- identity monad +type SaM a = a + +thenSa expr cont = cont expr + +thenSa_ expr cont = cont + +returnSa x = x + +tickLambda var = panic "OMIT_STRANAL_STATS: tickLambda" +tickCases vars = panic "OMIT_STRANAL_STATS: tickCases" +tickLet var = panic "OMIT_STRANAL_STATS: tickLet" + +#endif /* OMIT_STRANAL_STATS */ + +mapSa :: (a -> SaM b) -> [a] -> SaM [b] + +mapSa f [] = returnSa [] +mapSa f (x:xs) = f x `thenSa` \ r -> + mapSa f xs `thenSa` \ rs -> + returnSa (r:rs) + +sequenceSa :: [SaM a] -> SaM [a] +sequenceSa [] = returnSa [] +sequenceSa (m:ms) = m `thenSa` \ r -> + sequenceSa ms `thenSa` \ rs -> + returnSa (r:rs) + +#endif /* OLD_STRICTNESS */ +\end{code} diff --git a/compiler/stranal/WorkWrap.lhs b/compiler/stranal/WorkWrap.lhs new file mode 100644 index 0000000000..64eba89273 --- /dev/null +++ b/compiler/stranal/WorkWrap.lhs @@ -0,0 +1,403 @@ +% +% (c) The GRASP/AQUA Project, Glasgow University, 1993-1998 +% +\section[WorkWrap]{Worker/wrapper-generating back-end of strictness analyser} + +\begin{code} +module WorkWrap ( wwTopBinds, mkWrapper ) where + +#include "HsVersions.h" + +import CoreSyn +import CoreUnfold ( certainlyWillInline ) +import CoreLint ( showPass, endPass ) +import CoreUtils ( exprType, exprIsHNF ) +import Id ( Id, idType, isOneShotLambda, + setIdNewStrictness, mkWorkerId, + setIdWorkerInfo, setInlinePragma, + idInfo ) +import MkId ( lazyIdKey, lazyIdUnfolding ) +import Type ( Type ) +import IdInfo ( WorkerInfo(..), arityInfo, + newDemandInfo, newStrictnessInfo, unfoldingInfo, inlinePragInfo + ) +import NewDemand ( Demand(..), StrictSig(..), DmdType(..), DmdResult(..), + Demands(..), mkTopDmdType, isBotRes, returnsCPR, topSig, isAbsent + ) +import UniqSupply ( UniqSupply, initUs_, returnUs, thenUs, mapUs, getUniqueUs, UniqSM ) +import Unique ( hasKey ) +import BasicTypes ( RecFlag(..), isNonRec, Activation(..) ) +import VarEnv ( isEmptyVarEnv ) +import Maybes ( orElse ) +import DynFlags +import WwLib +import Util ( lengthIs, notNull ) +import Outputable +\end{code} + +We take Core bindings whose binders have: + +\begin{enumerate} + +\item Strictness attached (by the front-end of the strictness +analyser), and / or + +\item Constructed Product Result information attached by the CPR +analysis pass. + +\end{enumerate} + +and we return some ``plain'' bindings which have been +worker/wrapper-ified, meaning: + +\begin{enumerate} + +\item Functions have been split into workers and wrappers where +appropriate. If a function has both strictness and CPR properties +then only one worker/wrapper doing both transformations is produced; + +\item Binders' @IdInfos@ have been updated to reflect the existence of +these workers/wrappers (this is where we get STRICTNESS and CPR pragma +info for exported values). +\end{enumerate} + +\begin{code} + +wwTopBinds :: DynFlags + -> UniqSupply + -> [CoreBind] + -> IO [CoreBind] + +wwTopBinds dflags us binds + = do { + showPass dflags "Worker Wrapper binds"; + + -- Create worker/wrappers, and mark binders with their + -- "strictness info" [which encodes their worker/wrapper-ness] + let { binds' = workersAndWrappers us binds }; + + endPass dflags "Worker Wrapper binds" + Opt_D_dump_worker_wrapper binds' + } +\end{code} + + +\begin{code} +workersAndWrappers :: UniqSupply -> [CoreBind] -> [CoreBind] + +workersAndWrappers us top_binds + = initUs_ us $ + mapUs wwBind top_binds `thenUs` \ top_binds' -> + returnUs (concat top_binds') +\end{code} + +%************************************************************************ +%* * +\subsection[wwBind-wwExpr]{@wwBind@ and @wwExpr@} +%* * +%************************************************************************ + +@wwBind@ works on a binding, trying each \tr{(binder, expr)} pair in +turn. Non-recursive case first, then recursive... + +\begin{code} +wwBind :: CoreBind + -> UniqSM [CoreBind] -- returns a WwBinding intermediate form; + -- the caller will convert to Expr/Binding, + -- as appropriate. + +wwBind (NonRec binder rhs) + = wwExpr rhs `thenUs` \ new_rhs -> + tryWW NonRecursive binder new_rhs `thenUs` \ new_pairs -> + returnUs [NonRec b e | (b,e) <- new_pairs] + -- Generated bindings must be non-recursive + -- because the original binding was. + +wwBind (Rec pairs) + = mapUs do_one pairs `thenUs` \ new_pairs -> + returnUs [Rec (concat new_pairs)] + where + do_one (binder, rhs) = wwExpr rhs `thenUs` \ new_rhs -> + tryWW Recursive binder new_rhs +\end{code} + +@wwExpr@ basically just walks the tree, looking for appropriate +annotations that can be used. Remember it is @wwBind@ that does the +matching by looking for strict arguments of the correct type. +@wwExpr@ is a version that just returns the ``Plain'' Tree. + +\begin{code} +wwExpr :: CoreExpr -> UniqSM CoreExpr + +wwExpr e@(Type _) = returnUs e +wwExpr e@(Lit _) = returnUs e +wwExpr e@(Note InlineMe expr) = returnUs e + -- Don't w/w inside InlineMe's + +wwExpr e@(Var v) + | v `hasKey` lazyIdKey = returnUs lazyIdUnfolding + | otherwise = returnUs e + -- Inline 'lazy' after strictness analysis + -- (but not inside InlineMe's) + +wwExpr (Lam binder expr) + = wwExpr expr `thenUs` \ new_expr -> + returnUs (Lam binder new_expr) + +wwExpr (App f a) + = wwExpr f `thenUs` \ new_f -> + wwExpr a `thenUs` \ new_a -> + returnUs (App new_f new_a) + +wwExpr (Note note expr) + = wwExpr expr `thenUs` \ new_expr -> + returnUs (Note note new_expr) + +wwExpr (Let bind expr) + = wwBind bind `thenUs` \ intermediate_bind -> + wwExpr expr `thenUs` \ new_expr -> + returnUs (mkLets intermediate_bind new_expr) + +wwExpr (Case expr binder ty alts) + = wwExpr expr `thenUs` \ new_expr -> + mapUs ww_alt alts `thenUs` \ new_alts -> + returnUs (Case new_expr binder ty new_alts) + where + ww_alt (con, binders, rhs) + = wwExpr rhs `thenUs` \ new_rhs -> + returnUs (con, binders, new_rhs) +\end{code} + +%************************************************************************ +%* * +\subsection[tryWW]{@tryWW@: attempt a worker/wrapper pair} +%* * +%************************************************************************ + +@tryWW@ just accumulates arguments, converts strictness info from the +front-end into the proper form, then calls @mkWwBodies@ to do +the business. + +We have to BE CAREFUL that we don't worker-wrapperize an Id that has +already been w-w'd! (You can end up with several liked-named Ids +bouncing around at the same time---absolute mischief.) So the +criterion we use is: if an Id already has an unfolding (for whatever +reason), then we don't w-w it. + +The only reason this is monadised is for the unique supply. + +\begin{code} +tryWW :: RecFlag + -> Id -- The fn binder + -> CoreExpr -- The bound rhs; its innards + -- are already ww'd + -> UniqSM [(Id, CoreExpr)] -- either *one* or *two* pairs; + -- if one, then no worker (only + -- the orig "wrapper" lives on); + -- if two, then a worker and a + -- wrapper. +tryWW is_rec fn_id rhs + | isNonRec is_rec && certainlyWillInline unfolding + -- No point in worker/wrappering a function that is going to be + -- INLINEd wholesale anyway. If the strictness analyser is run + -- twice, this test also prevents wrappers (which are INLINEd) + -- from being re-done. + -- + -- It's very important to refrain from w/w-ing an INLINE function + -- If we do so by mistake we transform + -- f = __inline (\x -> E) + -- into + -- f = __inline (\x -> case x of (a,b) -> fw E) + -- fw = \ab -> (__inline (\x -> E)) (a,b) + -- and the original __inline now vanishes, so E is no longer + -- inside its __inline wrapper. Death! Disaster! + = returnUs [ (new_fn_id, rhs) ] + + | is_thunk && worthSplittingThunk maybe_fn_dmd res_info + = ASSERT2( isNonRec is_rec, ppr new_fn_id ) -- The thunk must be non-recursive + splitThunk new_fn_id rhs + + | is_fun && worthSplittingFun wrap_dmds res_info + = splitFun new_fn_id fn_info wrap_dmds res_info inline_prag rhs + + | otherwise + = returnUs [ (new_fn_id, rhs) ] + + where + fn_info = idInfo fn_id + maybe_fn_dmd = newDemandInfo fn_info + unfolding = unfoldingInfo fn_info + inline_prag = inlinePragInfo fn_info + + -- In practice it always will have a strictness + -- signature, even if it's a uninformative one + strict_sig = newStrictnessInfo fn_info `orElse` topSig + StrictSig (DmdType env wrap_dmds res_info) = strict_sig + + -- new_fn_id has the DmdEnv zapped. + -- (a) it is never used again + -- (b) it wastes space + -- (c) it becomes incorrect as things are cloned, because + -- we don't push the substitution into it + new_fn_id | isEmptyVarEnv env = fn_id + | otherwise = fn_id `setIdNewStrictness` + StrictSig (mkTopDmdType wrap_dmds res_info) + + is_fun = notNull wrap_dmds + is_thunk = not is_fun && not (exprIsHNF rhs) + +--------------------- +splitFun fn_id fn_info wrap_dmds res_info inline_prag rhs + = WARN( not (wrap_dmds `lengthIs` arity), ppr fn_id <+> (ppr arity $$ ppr wrap_dmds $$ ppr res_info) ) + -- The arity should match the signature + mkWwBodies fun_ty wrap_dmds res_info one_shots `thenUs` \ (work_demands, wrap_fn, work_fn) -> + getUniqueUs `thenUs` \ work_uniq -> + let + work_rhs = work_fn rhs + work_id = mkWorkerId work_uniq fn_id (exprType work_rhs) + `setInlinePragma` inline_prag + `setIdNewStrictness` StrictSig (mkTopDmdType work_demands work_res_info) + -- Even though we may not be at top level, + -- it's ok to give it an empty DmdEnv + + wrap_rhs = wrap_fn work_id + wrap_id = fn_id `setIdWorkerInfo` HasWorker work_id arity + `setInlinePragma` AlwaysActive -- Zap any inline pragma; + -- Put it on the worker instead + in + returnUs ([(work_id, work_rhs), (wrap_id, wrap_rhs)]) + -- Worker first, because wrapper mentions it + -- mkWwBodies has already built a wrap_rhs with an INLINE pragma wrapped around it + where + fun_ty = idType fn_id + + arity = arityInfo fn_info -- The arity is set by the simplifier using exprEtaExpandArity + -- So it may be more than the number of top-level-visible lambdas + + work_res_info | isBotRes res_info = BotRes -- Cpr stuff done by wrapper + | otherwise = TopRes + + one_shots = get_one_shots rhs + +-- If the original function has one-shot arguments, it is important to +-- make the wrapper and worker have corresponding one-shot arguments too. +-- Otherwise we spuriously float stuff out of case-expression join points, +-- which is very annoying. +get_one_shots (Lam b e) + | isId b = isOneShotLambda b : get_one_shots e + | otherwise = get_one_shots e +get_one_shots (Note _ e) = get_one_shots e +get_one_shots other = noOneShotInfo +\end{code} + +Thunk splitting +~~~~~~~~~~~~~~~ +Suppose x is used strictly (never mind whether it has the CPR +property). + + let + x* = x-rhs + in body + +splitThunk transforms like this: + + let + x* = case x-rhs of { I# a -> I# a } + in body + +Now simplifier will transform to + + case x-rhs of + I# a -> let x* = I# b + in body + +which is what we want. Now suppose x-rhs is itself a case: + + x-rhs = case e of { T -> I# a; F -> I# b } + +The join point will abstract over a, rather than over (which is +what would have happened before) which is fine. + +Notice that x certainly has the CPR property now! + +In fact, splitThunk uses the function argument w/w splitting +function, so that if x's demand is deeper (say U(U(L,L),L)) +then the splitting will go deeper too. + +\begin{code} +-- splitThunk converts the *non-recursive* binding +-- x = e +-- into +-- x = let x = e +-- in case x of +-- I# y -> let x = I# y in x } +-- See comments above. Is it not beautifully short? + +splitThunk fn_id rhs + = mkWWstr [fn_id] `thenUs` \ (_, wrap_fn, work_fn) -> + returnUs [ (fn_id, Let (NonRec fn_id rhs) (wrap_fn (work_fn (Var fn_id)))) ] +\end{code} + + +%************************************************************************ +%* * +\subsection{Functions over Demands} +%* * +%************************************************************************ + +\begin{code} +worthSplittingFun :: [Demand] -> DmdResult -> Bool + -- True <=> the wrapper would not be an identity function +worthSplittingFun ds res + = any worth_it ds || returnsCPR res + -- worthSplitting returns False for an empty list of demands, + -- and hence do_strict_ww is False if arity is zero and there is no CPR + + -- We used not to split if the result is bottom. + -- [Justification: there's no efficiency to be gained.] + -- But it's sometimes bad not to make a wrapper. Consider + -- fw = \x# -> let x = I# x# in case e of + -- p1 -> error_fn x + -- p2 -> error_fn x + -- p3 -> the real stuff + -- The re-boxing code won't go away unless error_fn gets a wrapper too. + -- [We don't do reboxing now, but in general it's better to pass + -- an unboxed thing to f, and have it reboxed in the error cases....] + where + worth_it Abs = True -- Absent arg + worth_it (Eval (Prod ds)) = True -- Product arg to evaluate + worth_it other = False + +worthSplittingThunk :: Maybe Demand -- Demand on the thunk + -> DmdResult -- CPR info for the thunk + -> Bool +worthSplittingThunk maybe_dmd res + = worth_it maybe_dmd || returnsCPR res + where + -- Split if the thing is unpacked + worth_it (Just (Eval (Prod ds))) = not (all isAbsent ds) + worth_it other = False +\end{code} + + + +%************************************************************************ +%* * +\subsection{The worker wrapper core} +%* * +%************************************************************************ + +@mkWrapper@ is called when importing a function. We have the type of +the function and the name of its worker, and we want to make its body (the wrapper). + +\begin{code} +mkWrapper :: Type -- Wrapper type + -> StrictSig -- Wrapper strictness info + -> UniqSM (Id -> CoreExpr) -- Wrapper body, missing worker Id + +mkWrapper fun_ty (StrictSig (DmdType _ demands res_info)) + = mkWwBodies fun_ty demands res_info noOneShotInfo `thenUs` \ (_, wrap_fn, _) -> + returnUs wrap_fn + +noOneShotInfo = repeat False +\end{code} diff --git a/compiler/stranal/WwLib.lhs b/compiler/stranal/WwLib.lhs new file mode 100644 index 0000000000..e44e521c83 --- /dev/null +++ b/compiler/stranal/WwLib.lhs @@ -0,0 +1,514 @@ +% +% (c) The GRASP/AQUA Project, Glasgow University, 1993-1998 +% +\section[WwLib]{A library for the ``worker/wrapper'' back-end to the strictness analyser} + +\begin{code} +module WwLib ( mkWwBodies, mkWWstr, mkWorkerArgs ) where + +#include "HsVersions.h" + +import CoreSyn +import CoreUtils ( exprType ) +import Id ( Id, idType, mkSysLocal, idNewDemandInfo, setIdNewDemandInfo, + isOneShotLambda, setOneShotLambda, setIdUnfolding, + setIdInfo + ) +import IdInfo ( vanillaIdInfo ) +import DataCon ( splitProductType_maybe, splitProductType ) +import NewDemand ( Demand(..), DmdResult(..), Demands(..) ) +import MkId ( realWorldPrimId, voidArgId, mkRuntimeErrorApp, rUNTIME_ERROR_ID ) +import TysWiredIn ( tupleCon ) +import Type ( Type, isUnLiftedType, mkFunTys, + splitForAllTys, splitFunTys, splitRecNewType_maybe, isAlgType + ) +import BasicTypes ( Boxity(..) ) +import Var ( Var, isId ) +import UniqSupply ( returnUs, thenUs, getUniquesUs, UniqSM ) +import Util ( zipWithEqual, notNull ) +import Outputable +import List ( zipWith4 ) +\end{code} + + +%************************************************************************ +%* * +\subsection[mkWrapperAndWorker]{@mkWrapperAndWorker@} +%* * +%************************************************************************ + +Here's an example. The original function is: + +\begin{verbatim} +g :: forall a . Int -> [a] -> a + +g = /\ a -> \ x ys -> + case x of + 0 -> head ys + _ -> head (tail ys) +\end{verbatim} + +From this, we want to produce: +\begin{verbatim} +-- wrapper (an unfolding) +g :: forall a . Int -> [a] -> a + +g = /\ a -> \ x ys -> + case x of + I# x# -> $wg a x# ys + -- call the worker; don't forget the type args! + +-- worker +$wg :: forall a . Int# -> [a] -> a + +$wg = /\ a -> \ x# ys -> + let + x = I# x# + in + case x of -- note: body of g moved intact + 0 -> head ys + _ -> head (tail ys) +\end{verbatim} + +Something we have to be careful about: Here's an example: + +\begin{verbatim} +-- "f" strictness: U(P)U(P) +f (I# a) (I# b) = a +# b + +g = f -- "g" strictness same as "f" +\end{verbatim} + +\tr{f} will get a worker all nice and friendly-like; that's good. +{\em But we don't want a worker for \tr{g}}, even though it has the +same strictness as \tr{f}. Doing so could break laziness, at best. + +Consequently, we insist that the number of strictness-info items is +exactly the same as the number of lambda-bound arguments. (This is +probably slightly paranoid, but OK in practice.) If it isn't the +same, we ``revise'' the strictness info, so that we won't propagate +the unusable strictness-info into the interfaces. + + +%************************************************************************ +%* * +\subsection{The worker wrapper core} +%* * +%************************************************************************ + +@mkWwBodies@ is called when doing the worker/wrapper split inside a module. + +\begin{code} +mkWwBodies :: Type -- Type of original function + -> [Demand] -- Strictness of original function + -> DmdResult -- Info about function result + -> [Bool] -- One-shot-ness of the function + -> UniqSM ([Demand], -- Demands for worker (value) args + Id -> CoreExpr, -- Wrapper body, lacking only the worker Id + CoreExpr -> CoreExpr) -- Worker body, lacking the original function rhs + +-- wrap_fn_args E = \x y -> E +-- work_fn_args E = E x y + +-- wrap_fn_str E = case x of { (a,b) -> +-- case a of { (a1,a2) -> +-- E a1 a2 b y }} +-- work_fn_str E = \a2 a2 b y -> +-- let a = (a1,a2) in +-- let x = (a,b) in +-- E + +mkWwBodies fun_ty demands res_info one_shots + = mkWWargs fun_ty demands one_shots' `thenUs` \ (wrap_args, wrap_fn_args, work_fn_args, res_ty) -> + mkWWstr wrap_args `thenUs` \ (work_args, wrap_fn_str, work_fn_str) -> + let + (work_lam_args, work_call_args) = mkWorkerArgs work_args res_ty + in + -- Don't do CPR if the worker doesn't have any value arguments + -- Then the worker is just a constant, so we don't want to unbox it. + (if any isId work_args then + mkWWcpr res_ty res_info + else + returnUs (id, id, res_ty) + ) `thenUs` \ (wrap_fn_cpr, work_fn_cpr, cpr_res_ty) -> + + returnUs ([idNewDemandInfo v | v <- work_args, isId v], + Note InlineMe . wrap_fn_args . wrap_fn_cpr . wrap_fn_str . applyToVars work_call_args . Var, + mkLams work_lam_args. work_fn_str . work_fn_cpr . work_fn_args) + -- We use an INLINE unconditionally, even if the wrapper turns out to be + -- something trivial like + -- fw = ... + -- f = __inline__ (coerce T fw) + -- The point is to propagate the coerce to f's call sites, so even though + -- f's RHS is now trivial (size 1) we still want the __inline__ to prevent + -- fw from being inlined into f's RHS + where + one_shots' = one_shots ++ repeat False +\end{code} + + +%************************************************************************ +%* * +\subsection{Making wrapper args} +%* * +%************************************************************************ + +During worker-wrapper stuff we may end up with an unlifted thing +which we want to let-bind without losing laziness. So we +add a void argument. E.g. + + f = /\a -> \x y z -> E::Int# -- E does not mention x,y,z +==> + fw = /\ a -> \void -> E + f = /\ a -> \x y z -> fw realworld + +We use the state-token type which generates no code. + +\begin{code} +mkWorkerArgs :: [Var] + -> Type -- Type of body + -> ([Var], -- Lambda bound args + [Var]) -- Args at call site +mkWorkerArgs args res_ty + | any isId args || not (isUnLiftedType res_ty) + = (args, args) + | otherwise + = (args ++ [voidArgId], args ++ [realWorldPrimId]) +\end{code} + + +%************************************************************************ +%* * +\subsection{Coercion stuff} +%* * +%************************************************************************ + + +We really want to "look through" coerces. +Reason: I've seen this situation: + + let f = coerce T (\s -> E) + in \x -> case x of + p -> coerce T' f + q -> \s -> E2 + r -> coerce T' f + +If only we w/w'd f, we'd get + let f = coerce T (\s -> fw s) + fw = \s -> E + in ... + +Now we'll inline f to get + + let fw = \s -> E + in \x -> case x of + p -> fw + q -> \s -> E2 + r -> fw + +Now we'll see that fw has arity 1, and will arity expand +the \x to get what we want. + +\begin{code} +-- mkWWargs is driven off the function type and arity. +-- It chomps bites off foralls, arrows, newtypes +-- and keeps repeating that until it's satisfied the supplied arity + +mkWWargs :: Type + -> [Demand] + -> [Bool] -- True for a one-shot arg; ** may be infinite ** + -> UniqSM ([Var], -- Wrapper args + CoreExpr -> CoreExpr, -- Wrapper fn + CoreExpr -> CoreExpr, -- Worker fn + Type) -- Type of wrapper body + +mkWWargs fun_ty demands one_shots + | Just rep_ty <- splitRecNewType_maybe fun_ty + -- The newtype case is for when the function has + -- a recursive newtype after the arrow (rare) + -- We check for arity >= 0 to avoid looping in the case + -- of a function whose type is, in effect, infinite + -- [Arity is driven by looking at the term, not just the type.] + -- + -- It's also important when we have a function returning (say) a pair + -- wrapped in a recursive newtype, at least if CPR analysis can look + -- through such newtypes, which it probably can since they are + -- simply coerces. + = mkWWargs rep_ty demands one_shots `thenUs` \ (wrap_args, wrap_fn_args, work_fn_args, res_ty) -> + returnUs (wrap_args, + Note (Coerce fun_ty rep_ty) . wrap_fn_args, + work_fn_args . Note (Coerce rep_ty fun_ty), + res_ty) + + | notNull demands + = getUniquesUs `thenUs` \ wrap_uniqs -> + let + (tyvars, tau) = splitForAllTys fun_ty + (arg_tys, body_ty) = splitFunTys tau + + n_demands = length demands + n_arg_tys = length arg_tys + n_args = n_demands `min` n_arg_tys + + new_fun_ty = mkFunTys (drop n_demands arg_tys) body_ty + new_demands = drop n_arg_tys demands + new_one_shots = drop n_args one_shots + + val_args = zipWith4 mk_wrap_arg wrap_uniqs arg_tys demands one_shots + wrap_args = tyvars ++ val_args + in +{- ASSERT( notNull tyvars || notNull arg_tys ) -} + if (null tyvars) && (null arg_tys) then + pprTrace "mkWWargs" (ppr fun_ty $$ ppr demands) + returnUs ([], id, id, fun_ty) + else + + mkWWargs new_fun_ty + new_demands + new_one_shots `thenUs` \ (more_wrap_args, wrap_fn_args, work_fn_args, res_ty) -> + + returnUs (wrap_args ++ more_wrap_args, + mkLams wrap_args . wrap_fn_args, + work_fn_args . applyToVars wrap_args, + res_ty) + + | otherwise + = returnUs ([], id, id, fun_ty) + + +applyToVars :: [Var] -> CoreExpr -> CoreExpr +applyToVars vars fn = mkVarApps fn vars + +mk_wrap_arg uniq ty dmd one_shot + = set_one_shot one_shot (setIdNewDemandInfo (mkSysLocal FSLIT("w") uniq ty) dmd) + where + set_one_shot True id = setOneShotLambda id + set_one_shot False id = id +\end{code} + + +%************************************************************************ +%* * +\subsection{Strictness stuff} +%* * +%************************************************************************ + +\begin{code} +mkWWstr :: [Var] -- Wrapper args; have their demand info on them + -- *Includes type variables* + -> UniqSM ([Var], -- Worker args + CoreExpr -> CoreExpr, -- Wrapper body, lacking the worker call + -- and without its lambdas + -- This fn adds the unboxing + + CoreExpr -> CoreExpr) -- Worker body, lacking the original body of the function, + -- and lacking its lambdas. + -- This fn does the reboxing + +---------------------- +nop_fn body = body + +---------------------- +mkWWstr [] + = returnUs ([], nop_fn, nop_fn) + +mkWWstr (arg : args) + = mkWWstr_one arg `thenUs` \ (args1, wrap_fn1, work_fn1) -> + mkWWstr args `thenUs` \ (args2, wrap_fn2, work_fn2) -> + returnUs (args1 ++ args2, wrap_fn1 . wrap_fn2, work_fn1 . work_fn2) + + +---------------------- +-- mkWWstr_one wrap_arg = (work_args, wrap_fn, work_fn) +-- * wrap_fn assumes wrap_arg is in scope, +-- brings into scope work_args (via cases) +-- * work_fn assumes work_args are in scope, a +-- brings into scope wrap_arg (via lets) + +mkWWstr_one arg + | isTyVar arg + = returnUs ([arg], nop_fn, nop_fn) + + | otherwise + = case idNewDemandInfo arg of + + -- Absent case. We don't deal with absence for unlifted types, + -- though, because it's not so easy to manufacture a placeholder + -- We'll see if this turns out to be a problem + Abs | not (isUnLiftedType (idType arg)) -> + returnUs ([], nop_fn, mk_absent_let arg) + + -- Unpack case + Eval (Prod cs) + | Just (arg_tycon, tycon_arg_tys, data_con, inst_con_arg_tys) + <- splitProductType_maybe (idType arg) + -> getUniquesUs `thenUs` \ uniqs -> + let + unpk_args = zipWith mk_ww_local uniqs inst_con_arg_tys + unpk_args_w_ds = zipWithEqual "mkWWstr" set_worker_arg_info unpk_args cs + unbox_fn = mk_unpk_case arg unpk_args data_con arg_tycon + rebox_fn = Let (NonRec arg con_app) + con_app = mkConApp data_con (map Type tycon_arg_tys ++ map Var unpk_args) + in + mkWWstr unpk_args_w_ds `thenUs` \ (worker_args, wrap_fn, work_fn) -> + returnUs (worker_args, unbox_fn . wrap_fn, work_fn . rebox_fn) + -- Don't pass the arg, rebox instead + + -- `seq` demand; evaluate in wrapper in the hope + -- of dropping seqs in the worker + Eval (Poly Abs) + -> let + arg_w_unf = arg `setIdUnfolding` evaldUnfolding + -- Tell the worker arg that it's sure to be evaluated + -- so that internal seqs can be dropped + in + returnUs ([arg_w_unf], mk_seq_case arg, nop_fn) + -- Pass the arg, anyway, even if it is in theory discarded + -- Consider + -- f x y = x `seq` y + -- x gets a (Eval (Poly Abs)) demand, but if we fail to pass it to the worker + -- we ABSOLUTELY MUST record that x is evaluated in the wrapper. + -- Something like: + -- f x y = x `seq` fw y + -- fw y = let x{Evald} = error "oops" in (x `seq` y) + -- If we don't pin on the "Evald" flag, the seq doesn't disappear, and + -- we end up evaluating the absent thunk. + -- But the Evald flag is pretty weird, and I worry that it might disappear + -- during simplification, so for now I've just nuked this whole case + + -- Other cases + other_demand -> returnUs ([arg], nop_fn, nop_fn) + + where + -- If the wrapper argument is a one-shot lambda, then + -- so should (all) the corresponding worker arguments be + -- This bites when we do w/w on a case join point + set_worker_arg_info worker_arg demand = set_one_shot (setIdNewDemandInfo worker_arg demand) + + set_one_shot | isOneShotLambda arg = setOneShotLambda + | otherwise = \x -> x +\end{code} + + +%************************************************************************ +%* * +\subsection{CPR stuff} +%* * +%************************************************************************ + + +@mkWWcpr@ takes the worker/wrapper pair produced from the strictness +info and adds in the CPR transformation. The worker returns an +unboxed tuple containing non-CPR components. The wrapper takes this +tuple and re-produces the correct structured output. + +The non-CPR results appear ordered in the unboxed tuple as if by a +left-to-right traversal of the result structure. + + +\begin{code} +mkWWcpr :: Type -- function body type + -> DmdResult -- CPR analysis results + -> UniqSM (CoreExpr -> CoreExpr, -- New wrapper + CoreExpr -> CoreExpr, -- New worker + Type) -- Type of worker's body + +mkWWcpr body_ty RetCPR + | not (isAlgType body_ty) + = WARN( True, text "mkWWcpr: non-algebraic body type" <+> ppr body_ty ) + returnUs (id, id, body_ty) + + | n_con_args == 1 && isUnLiftedType con_arg_ty1 + -- Special case when there is a single result of unlifted type + -- + -- Wrapper: case (..call worker..) of x -> C x + -- Worker: case ( ..body.. ) of C x -> x + = getUniquesUs `thenUs` \ (work_uniq : arg_uniq : _) -> + let + work_wild = mk_ww_local work_uniq body_ty + arg = mk_ww_local arg_uniq con_arg_ty1 + con_app = mkConApp data_con (map Type tycon_arg_tys ++ [Var arg]) + in + returnUs (\ wkr_call -> Case wkr_call arg (exprType con_app) [(DEFAULT, [], con_app)], + \ body -> workerCase body work_wild con_arg_ty1 [(DataAlt data_con, [arg], Var arg)], + con_arg_ty1) + + | otherwise -- The general case + -- Wrapper: case (..call worker..) of (# a, b #) -> C a b + -- Worker: case ( ...body... ) of C a b -> (# a, b #) + = getUniquesUs `thenUs` \ uniqs -> + let + (wrap_wild : work_wild : args) = zipWith mk_ww_local uniqs (ubx_tup_ty : body_ty : con_arg_tys) + arg_vars = map Var args + ubx_tup_con = tupleCon Unboxed n_con_args + ubx_tup_ty = exprType ubx_tup_app + ubx_tup_app = mkConApp ubx_tup_con (map Type con_arg_tys ++ arg_vars) + con_app = mkConApp data_con (map Type tycon_arg_tys ++ arg_vars) + in + returnUs (\ wkr_call -> Case wkr_call wrap_wild (exprType con_app) [(DataAlt ubx_tup_con, args, con_app)], + \ body -> workerCase body work_wild ubx_tup_ty [(DataAlt data_con, args, ubx_tup_app)], + ubx_tup_ty) + where + (_, tycon_arg_tys, data_con, con_arg_tys) = splitProductType "mkWWcpr" body_ty + n_con_args = length con_arg_tys + con_arg_ty1 = head con_arg_tys + +mkWWcpr body_ty other -- No CPR info + = returnUs (id, id, body_ty) + +-- If the original function looked like +-- f = \ x -> _scc_ "foo" E +-- +-- then we want the CPR'd worker to look like +-- \ x -> _scc_ "foo" (case E of I# x -> x) +-- and definitely not +-- \ x -> case (_scc_ "foo" E) of I# x -> x) +-- +-- This transform doesn't move work or allocation +-- from one cost centre to another + +workerCase (Note (SCC cc) e) arg ty alts = Note (SCC cc) (Case e arg ty alts) +workerCase e arg ty alts = Case e arg ty alts +\end{code} + + +%************************************************************************ +%* * +\subsection{Utilities} +%* * +%************************************************************************ + + +\begin{code} +mk_absent_let arg body + | not (isUnLiftedType arg_ty) + = Let (NonRec arg abs_rhs) body + | otherwise + = panic "WwLib: haven't done mk_absent_let for primitives yet" + where + arg_ty = idType arg + abs_rhs = mkRuntimeErrorApp rUNTIME_ERROR_ID arg_ty msg + msg = "Oops! Entered absent arg " ++ showSDocDebug (ppr arg <+> ppr (idType arg)) + +mk_unpk_case arg unpk_args boxing_con boxing_tycon body + -- A data type + = Case (Var arg) + (sanitiseCaseBndr arg) + (exprType body) + [(DataAlt boxing_con, unpk_args, body)] + +mk_seq_case arg body = Case (Var arg) (sanitiseCaseBndr arg) (exprType body) [(DEFAULT, [], body)] + +sanitiseCaseBndr :: Id -> Id +-- The argument we are scrutinising has the right type to be +-- a case binder, so it's convenient to re-use it for that purpose. +-- But we *must* throw away all its IdInfo. In particular, the argument +-- will have demand info on it, and that demand info may be incorrect for +-- the case binder. e.g. case ww_arg of ww_arg { I# x -> ... } +-- Quite likely ww_arg isn't used in '...'. The case may get discarded +-- if the case binder says "I'm demanded". This happened in a situation +-- like (x+y) `seq` .... +sanitiseCaseBndr id = id `setIdInfo` vanillaIdInfo + +mk_ww_local uniq ty = mkSysLocal FSLIT("ww") uniq ty +\end{code} |