#include "HsVersions.h"
-import CmdLineOpts ( DynFlags, DynFlag(..) )
+import CmdLineOpts ( DynFlags, DynFlag(..), opt_MaxWorkerArgs )
import NewDemand -- All of it
import CoreSyn
import CoreUtils ( exprIsValue, exprArity )
import DataCon ( dataConTyCon )
import TyCon ( isProductTyCon, isRecursiveTyCon )
-import Id ( Id, idType, idInfo, idArity, idStrictness, idCprInfo, idDemandInfo,
- modifyIdInfo, isDataConId, isImplicitId, isGlobalId )
-import IdInfo ( newStrictnessInfo, setNewStrictnessInfo, mkNewStrictnessInfo,
- newDemandInfo, setNewDemandInfo, newDemand
- )
+import Id ( Id, idType, idInfo, idArity, idCprInfo, idDemandInfo,
+ modifyIdInfo, isDataConId, isImplicitId, isGlobalId,
+ idNewStrictness, idNewStrictness_maybe, getNewStrictness, setIdNewStrictness,
+ idNewDemandInfo, setIdNewDemandInfo, newStrictnessFromOld )
+import IdInfo ( newDemand )
import Var ( Var )
import VarEnv
import UniqFM ( plusUFM_C, addToUFM_Directly, lookupUFM_Directly,
import FastTypes
\end{code}
-ToDo: set a noinline pragma on bottoming Ids
+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.
+
\begin{code}
instance Outputable TopLevelFlag where
ppr flag = empty
\begin{code}
dmdAnalPgm :: DynFlags -> [CoreBind] -> IO [CoreBind]
-#ifndef DEBUG
-
-dmdAnalPgm dflags binds = return binds
-
-#else
-
+dmdAnalPgm dflags binds
+ = panic "dmdAnalPgm called"
dmdAnalPgm dflags binds
= do {
showPass dflags "Demand analysis" ;
dmd_changes = get_changes binds_plus_dmds } ;
endPass dflags "Demand analysis"
Opt_D_dump_stranal binds_plus_dmds ;
+#ifdef DEBUG
+ -- Only if DEBUG is on, because only then is the old strictness analyser run
printDump (text "Changes in demands" $$ dmd_changes) ;
+#endif
return binds_plus_dmds
}
where
dmdAnal sigs dmd (Note n e)
= (dmd_ty, Note n e')
where
- (dmd_ty, e') = dmdAnal sigs dmd e
+ (dmd_ty, e') = dmdAnal sigs dmd' e
+ dmd' = case n of
+ Coerce _ _ -> Eval -- 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))
isProductTyCon tycon,
not (isRecursiveTyCon tycon)
= let
- bndr_ids = filter isId bndrs
- (alt_ty, alt') = dmdAnalAlt sigs dmd alt
- (alt_ty1, case_bndr') = annotateBndr alt_ty case_bndr
- (_, bndrs', _) = alt'
- scrut_dmd = Seq Drop Now [idNewDemandInfo b | b <- bndrs', isId b]
- (scrut_ty, scrut') = dmdAnal sigs scrut_dmd scrut
+ bndr_ids = filter isId bndrs
+ (alt_ty, alt') = dmdAnalAlt sigs dmd alt
+ (alt_ty1, case_bndr') = annotateBndr alt_ty case_bndr
+ (_, bndrs', _) = alt'
+
+ -- Figure out whether the case binder is used, and use
+ -- that to set the keepity of the demand. 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.
+ dead_case_bndr = isAbsentDmd (idNewDemandInfo case_bndr')
+ keepity | dead_case_bndr = Drop
+ | otherwise = Keep
+
+ scrut_dmd = Seq keepity Now [idNewDemandInfo b | b <- bndrs', isId b]
+ (scrut_ty, scrut') = dmdAnal sigs scrut_dmd scrut
in
(alt_ty1 `bothType` scrut_ty, Case scrut' case_bndr' [alt'])
-- processing the RHSs with sigs (= sigs'), whereas pairs
-- is the result of processing the RHSs with the *previous*
-- iteration of sigs.
- | n >= 5 = pprTrace "dmdFix" (ppr n <+> ppr pairs) (loop (n+1) sigs' pairs')
+ | n >= 5 = pprTrace "dmdFix" (ppr n <+> (vcat
+ [ text "Sigs:" <+> ppr [(id,lookup sigs id, lookup sigs' id) | (id,_) <- pairs],
+ text "env:" <+> ppr (ufmToList sigs),
+ text "binds:" <+> ppr pairs]))
+ (loop (n+1) sigs' pairs')
| otherwise = {- pprTrace "dmdFixLoop" (ppr id_sigs) -} (loop (n+1) sigs' pairs')
where
-- Use the new signature to do the next pair
where
arity = exprArity rhs -- The idArity may not be up to date
(rhs_ty, rhs') = dmdAnal sigs (vanillaCall arity) rhs
- (lazy_fv, sig_ty) = mkSigTy rhs rhs_ty
- sig = mkStrictSig id arity sig_ty
- id' = id `setIdNewStrictness` sig
- sigs' = extendSigEnv top_lvl sigs id sig
+ (lazy_fv, sig_ty) = mkSigTy id arity rhs rhs_ty
+ id' = id `setIdNewStrictness` sig_ty
+ sigs' = extendSigEnv top_lvl sigs id sig_ty
+\end{code}
-mkSigTy rhs (DmdType fv dmds res)
- = (lazy_fv, DmdType strict_fv lazified_dmds res')
+%************************************************************************
+%* *
+\subsection{Strictness signatures and types}
+%* *
+%************************************************************************
+
+\begin{code}
+mkSigTy :: Id -> Arity -> CoreExpr -> DmdType -> (DmdEnv, StrictSig)
+-- Take a DmdType and turn it into a StrictSig
+mkSigTy id arity rhs (DmdType fv dmds res)
+ = (lazy_fv, mkStrictSig id arity dmd_ty)
where
+ dmd_ty = DmdType strict_fv lazified_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
lazified_dmds = map lazify dmds
-- Get rid of defers in the arguments
-
+ final_dmds = setUnpackStrategy lazified_dmds
+ -- Set the unpacking strategy
+
res' = case (dmds, res) of
([], RetCPR) | not (exprIsValue rhs) -> TopRes
other -> res
-- if r doesn't have the CPR property then neither does modInt
\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 (Seq keep _ cs : ds)
+ | n' >= 0 = Seq keep Now cs' `cons` go n'' ds
+ | otherwise = Eval `cons` go n ds
+ where
+ (n'',cs') = go n' cs
+ n' = n + box - non_abs_args
+ box = case keep of
+ Keep -> 0
+ Drop -> 1 -- Add one to the budget if 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}
+
%************************************************************************
%* *
%************************************************************************
\begin{code}
+splitDmdTy :: DmdType -> (Demand, DmdType)
+-- Split off one function argument
+splitDmdTy (DmdType fv (dmd:dmds) res_ty) = (dmd, DmdType fv dmds res_ty)
+splitDmdTy ty@(DmdType fv [] TopRes) = (topDmd, ty)
+splitDmdTy ty@(DmdType fv [] BotRes) = (Abs, ty)
+ -- We already have a suitable demand on all
+ -- free vars, so no need to add more!
+splitDmdTy (DmdType fv [] RetCPR) = panic "splitDmdTy"
+\end{code}
+
+\begin{code}
unitVarDmd var dmd = DmdType (unitVarEnv var dmd) [] TopRes
addVarDmd top_lvl dmd_ty@(DmdType fv ds res) var dmd
%************************************************************************
%* *
-\subsection{Demand types}
-%* *
-%************************************************************************
-
-\begin{code}
-splitDmdTy :: DmdType -> (Demand, DmdType)
--- Split off one function argument
-splitDmdTy (DmdType fv (dmd:dmds) res_ty) = (dmd, DmdType fv dmds res_ty)
-splitDmdTy ty@(DmdType fv [] TopRes) = (topDmd, ty)
-splitDmdTy ty@(DmdType fv [] BotRes) = (Abs, ty)
- -- We already have a suitable demand on all
- -- free vars, so no need to add more!
-splitDmdTy (DmdType fv [] RetCPR) = panic "splitDmdTy"
-
--------------------------
-dmdTypeRes :: DmdType -> DmdResult
-dmdTypeRes (DmdType _ _ res_ty) = res_ty
-\end{code}
-
-
-%************************************************************************
-%* *
\subsection{Strictness signatures}
%* *
%************************************************************************
------ DATA CONSTRUCTOR
| isDataConId var, -- Data constructor
Seq k Now ds <- res_dmd, -- and the demand looks inside its fields
- let StrictSig arity dmd_ty = idNewStrictness var -- It must have a strictness sig
- = if arity == length ds then -- Saturated, so unleash the demand
+ let StrictSig dmd_ty = idNewStrictness var -- It must have a strictness sig
+ = if dmdTypeDepth dmd_ty == length ds then -- Saturated, so unleash the demand
-- ds can be empty, when we are just seq'ing the thing
mkDmdType emptyDmdEnv ds (dmdTypeRes dmd_ty)
- -- Need to extract whether it's a product
+ -- Need to extract whether it's a product, hence dmdTypeRes
else
topDmdType
------ IMPORTED FUNCTION
| isGlobalId var, -- Imported function
- let StrictSig arity dmd_ty = getNewStrictness var
- = if arity <= depth then -- Saturated, so unleash the demand
+ let StrictSig dmd_ty = getNewStrictness var
+ = if dmdTypeDepth dmd_ty <= call_depth then -- Saturated, so unleash the demand
dmd_ty
else
topDmdType
------ LOCAL LET/REC BOUND THING
- | Just (StrictSig arity dmd_ty, top_lvl) <- lookupVarEnv sigs var
+ | Just (StrictSig dmd_ty, top_lvl) <- lookupVarEnv sigs var
= let
- fn_ty | arity <= depth = dmd_ty
- | otherwise = deferType dmd_ty
+ 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
= unitVarDmd var dmd
where
- (depth, res_dmd) = splitCallDmd dmd
-\end{code}
-
-\begin{code}
-squashDmdEnv (StrictSig a (DmdType fv ds res)) = StrictSig a (DmdType emptyDmdEnv ds res)
-
-betterStrict :: StrictSig -> StrictSig -> Bool
-betterStrict (StrictSig ar1 t1) (StrictSig ar2 t2)
- = (ar1 >= ar2) && (t1 `betterDmdType` t2)
-
-betterDmdType t1 t2 = (t1 `lubType` t2) == t2
+ (call_depth, res_dmd) = splitCallDmd dmd
\end{code}
defer (Seq k _ ds) = Seq k Defer ds
defer other = Lazy
-isStrictDmd :: Demand -> Bool
-isStrictDmd Bot = True
-isStrictDmd Err = True
-isStrictDmd (Seq _ Now _) = True
-isStrictDmd Eval = True
-isStrictDmd (Call _) = True
-isStrictDmd other = False
-
lazify :: Demand -> Demand
-- The 'Defer' demands are just Lazy at function boundaries
lazify (Seq k Defer ds) = Lazy
lazify (Seq k Now ds) = Seq k Now (map lazify ds)
lazify Bot = Abs -- Don't pass args that are consumed by bottom
lazify d = d
+\end{code}
+
+\begin{code}
+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
+
+squashDmdEnv (StrictSig (DmdType fv ds res)) = StrictSig (DmdType emptyDmdEnv ds res)
\end{code}
lub Abs (Seq k _ ds) = Seq k Defer ds -- Very important ('radicals' example)
lub Abs d = Lazy
-lub Eval Abs = Lazy
-lub Eval Lazy = Lazy
-lub Eval (Seq k Now ds) = Seq Keep Now ds
-lub Eval d = Eval
+lub Eval Abs = Lazy
+lub Eval Lazy = Lazy
+lub Eval (Seq k Now ds) = Seq Keep Now ds
+lub Eval (Seq k Defer ds) = Lazy
+lub Eval d = Eval
lub (Call d1) (Call d2) = Call (lub d1 d2)
-lub (Seq k1 l1 ds1) (Seq k2 l2 ds2) = Seq (k1 `vee` k2) (l1 `or_defer` l2)
- (zipWithEqual "lub" lub ds1 ds2)
+lub (Seq k1 l1 ds1) (Seq k2 l2 ds2) = Seq (k1 `vee` k2) (l1 `or_defer` l2) (lubs ds1 ds2)
-- The last clauses deal with the remaining cases for Call and Seq
lub d1@(Call _) d2@(Seq _ _ _) = pprPanic "lub" (ppr d1 $$ ppr d2)
lub d1 d2 = lub d2 d1
+-- A Seq can have an empty list of demands, in the polymorphic case.
+lubs [] ds2 = ds2
+lubs ds1 [] = ds1
+lubs ds1 ds2 = ASSERT( length ds1 == length ds2 ) zipWith lub ds1 ds2
+
or_defer Now Now = Now
or_defer _ _ = Defer
-- both Bot d = Bot
-- The experimental one
+-- The idea is that (error x) places on x
+-- both demand Bot (like on all free vars)
+-- and demand Eval (for the arg to error)
+-- and we want the result to be Eval.
both Bot Bot = Bot
both Bot Abs = Bot
both Bot d = d
-
-both Abs Bot = Bot
both Abs d = d
-both Err Bot = Bot
+both Err Bot = Err
both Err Abs = Err
both Err d = d
-both Lazy Bot = Bot
-both Lazy Abs = Lazy
-both Lazy Err = Lazy
-both Lazy (Seq k Now ds) = Seq Keep Now ds
-both Lazy d = d
+both Lazy Bot = Lazy
+both Lazy Abs = Lazy
+both Lazy Err = Lazy
+both Lazy (Seq k l ds) = Seq Keep l ds
+both Lazy d = d
+ -- Notice that the Seq case ensures that we have the
+ -- boxed value. The equation originally said
+ -- both (Seq k Now ds) = Seq Keep Now ds
+ -- but it's important that the Keep is switched on even
+ -- for a deferred demand. Otherwise a (Seq Drop Now [])
+ -- might both'd with the result, and then we won't pass
+ -- the boxed value. Here's an example:
+ -- (x-1) `seq` (x+1, x)
+ -- From the (x+1, x) we get (U*(V) `both` L), which must give S*(V)
+ -- From (x-1) we get U(V). Combining, we must get S(V).
+ -- If we got U*(V) from the pair, we'd end up with U(V), and that
+ -- can be a disaster if a component of the data structure is absent.
+ -- [Disaster = enter an absent argument.]
--- Part of the Bot like Err experiment
--- both Eval Bot = Bot
both Eval (Seq k l ds) = Seq Keep Now ds
both Eval (Call d) = Call d
both Eval d = Eval
-both (Seq k1 Defer ds1) (Seq k2 Defer ds2) = Seq (k1 `vee` k2) Defer
- (zipWithEqual "both" both ds1 ds2)
-both (Seq k1 l1 ds1) (Seq k2 l2 ds2) = Seq (k1 `vee` k2) Now
- (zipWithEqual "both" both ds1' ds2')
- where
- ds1' = case l1 of { Now -> ds1; Defer -> map defer ds1 }
- ds2' = case l2 of { Now -> ds2; Defer -> map defer ds2 }
+both (Seq k1 Defer ds1) (Seq k2 Defer ds2) = Seq (k1 `vee` k2) Defer (boths ds1 ds2)
+both (Seq k1 l1 ds1) (Seq k2 l2 ds2) = Seq (k1 `vee` k2) Now (boths ds1' ds2')
+ where
+ ds1' = case l1 of { Now -> ds1; Defer -> map defer ds1 }
+ ds2' = case l2 of { Now -> ds2; Defer -> map defer ds2 }
both (Call d1) (Call d2) = Call (d1 `both` d2)
both d1 d2 = both d2 d1
-----------------------------------
+-- A Seq can have an empty list of demands, in the polymorphic case.
+boths [] ds2 = ds2
+boths ds1 [] = ds1
+boths ds1 ds2 = ASSERT( length ds1 == length ds2 ) zipWith both ds1 ds2
+
+-----------------------------------
bothRes :: DmdResult -> DmdResult -> DmdResult
-- Left-biased for CPR info
bothRes BotRes _ = BotRes
\begin{code}
--- Move these to Id.lhs
-idNewStrictness_maybe :: Id -> Maybe StrictSig
-idNewStrictness :: Id -> StrictSig
-
-idNewStrictness_maybe id = newStrictnessInfo (idInfo id)
-idNewStrictness id = idNewStrictness_maybe id `orElse` topSig
-
-getNewStrictness :: Id -> StrictSig
--- First tries the "new-strictness" field, and then
--- reverts to the old one. This is just until we have
--- cross-module info for new strictness
-getNewStrictness id = idNewStrictness_maybe id `orElse` newStrictnessFromOld id
-
-newStrictnessFromOld :: Id -> StrictSig
-newStrictnessFromOld id = mkNewStrictnessInfo id (idArity id) (idStrictness id) (idCprInfo id)
-
-setIdNewStrictness :: Id -> StrictSig -> Id
-setIdNewStrictness id sig = modifyIdInfo (`setNewStrictnessInfo` sig) id
-
-idNewDemandInfo :: Id -> Demand
-idNewDemandInfo id = newDemandInfo (idInfo id)
-
-setIdNewDemandInfo :: Id -> Demand -> Id
-setIdNewDemandInfo id dmd = modifyIdInfo (`setNewDemandInfo` dmd) id
-\end{code}
-
-\begin{code}
get_changes binds = vcat (map get_changes_bind binds)
get_changes_bind (Rec pairs) = vcat (map get_changes_pr pairs)
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_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
info = (text "Old" <+> ppr old) $$ (text "New" <+> ppr new)
new = squashDmdEnv (idNewStrictness id) -- Don't report diffs in the env
old = newStrictnessFromOld id
- old_better = old `betterStrict` new
- new_better = new `betterStrict` old
+ old_better = old `betterStrictness` new
+ new_better = new `betterStrictness` old
get_changes_dmd id
| isUnLiftedType (idType id) = empty -- Not useful
old = newDemand (idDemandInfo id)
new_better = new `betterDemand` old
old_better = old `betterDemand` new
-#endif /* DEBUG */
\end{code}