summaryrefslogtreecommitdiff
path: root/compiler/GHC
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/GHC')
-rw-r--r--compiler/GHC/Core/Opt/DmdAnal.hs12
-rw-r--r--compiler/GHC/Types/Demand.hs98
2 files changed, 93 insertions, 17 deletions
diff --git a/compiler/GHC/Core/Opt/DmdAnal.hs b/compiler/GHC/Core/Opt/DmdAnal.hs
index 4869fb1fa9..ae8aab18a8 100644
--- a/compiler/GHC/Core/Opt/DmdAnal.hs
+++ b/compiler/GHC/Core/Opt/DmdAnal.hs
@@ -233,9 +233,10 @@ dmdAnal' env dmd (Case scrut case_bndr ty [(alt, bndrs, rhs)])
(rhs_ty, rhs') = dmdAnal env dmd rhs
(alt_ty1, dmds) = findBndrsDmds env rhs_ty bndrs
(alt_ty2, case_bndr_dmd) = findBndrDmd env False alt_ty1 case_bndr
- -- Evaluation cardinality on the case binder is irrelevant and a no-op.
- -- What matters is its nested sub-demand!
- (_ :* case_bndr_sd) = case_bndr_dmd
+ -- The peelDmd below will lazify the relative sub-demands if the
+ -- case_bndr_dmd had lazy evaluation cardinality.
+ -- See Note [Absent sub-demand] in GHC.Types.Demand
+ case_bndr_sd = peelDmd case_bndr_dmd
-- Compute demand on the scrutinee
(bndrs', scrut_sd)
| DataAlt _ <- alt
@@ -388,9 +389,10 @@ dmdAnalSumAlt :: AnalEnv -> SubDemand -> Id -> Alt Var -> (DmdType, Alt Var)
dmdAnalSumAlt env dmd case_bndr (con,bndrs,rhs)
| (rhs_ty, rhs') <- dmdAnal env dmd rhs
, (alt_ty, dmds) <- findBndrsDmds env rhs_ty bndrs
- , let (_ :* case_bndr_sd) = findIdDemand alt_ty case_bndr
+ , let case_bndr_dmd = findIdDemand alt_ty case_bndr
-- See Note [Demand on scrutinee of a product case]
- id_dmds = addCaseBndrDmd case_bndr_sd dmds
+ -- See Note [Absent sub-demand] in GHC.Types.Demand
+ id_dmds = addCaseBndrDmd (peelDmd case_bndr_dmd) dmds
= (alt_ty, (con, setBndrsDemandInfo bndrs id_dmds, rhs'))
{-
diff --git a/compiler/GHC/Types/Demand.hs b/compiler/GHC/Types/Demand.hs
index 2ebc2222b4..fe9a3eec9f 100644
--- a/compiler/GHC/Types/Demand.hs
+++ b/compiler/GHC/Types/Demand.hs
@@ -35,7 +35,7 @@ module GHC.Types.Demand (
-- ** Other @Demand@ operations
oneifyCard, oneifyDmd, strictifyDmd, strictifyDictDmd, mkWorkerDemand,
peelCallDmd, peelManyCalls, mkCallDmd, mkCallDmds,
- addCaseBndrDmd,
+ peelDmd, addCaseBndrDmd,
-- ** Extracting one-shot information
argOneShots, argsOneShots, saturatedByOneShots,
@@ -231,8 +231,9 @@ multCard _ _ = C_0N
-- * '$' puts demand @SCS(U)@ on its first argument: It calls (@C@) the
-- argument function with one argument, exactly once (@S@). No info
-- on how the result of that call is evaluated (@U@).
--- * 'maybe' puts demand @1C1(U)@ on its second argument: It evaluates
--- the argument function lazily and calls it once when it is evaluated.
+-- * 'maybe' puts demand @1CS(U)@ on its second argument: It evaluates
+-- the argument function lazily, but calls it exactly once when it is
+-- evaluated.
-- * @fst p + fst p@ puts demand @MP(MU,A)@ on @p@: It's @SP(SU,A)@
-- multiplied by two, so we get @M@ (used at least once, possibly multiple
-- times).
@@ -252,6 +253,7 @@ data Demand
-- @f@ is called exactly twice (@M@), each time exactly once (@S@) with an
-- additional argument.
--
+-- TODO: update following paragraph with intuition from #18885.
-- The nested 'Demand's @dn@ of a 'Prod' @P(d1,d2,...)@ apply /absolutely/:
-- If @dn@ is a used once demand (cf. 'isUsedOnce'), then that means that
-- the denoted sub-expression is used once in the entire evaluation context
@@ -367,6 +369,9 @@ lubSubDmd _ _ = topSubDmd
-- | Denotes '∪' on 'Demand'.
lubDmd :: Demand -> Demand -> Demand
+-- See Note [Absent sub-demand] for the first two special cases
+lubDmd (C_00 :* _) (n2 :* sd2) = lubCard C_00 n2 :* lubSubDmd botSubDmd sd2
+lubDmd (n1 :* sd1) (C_00 :* _) = lubCard n1 C_00 :* lubSubDmd sd1 botSubDmd
lubDmd (n1 :* sd1) (n2 :* sd2) = lubCard n1 n2 :* lubSubDmd sd1 sd2
-- | Denotes '+' on 'SubDemand'.
@@ -459,20 +464,20 @@ evalDmd = C_1N :* topSubDmd
strictOnceApply1Dmd :: Demand
strictOnceApply1Dmd = C_11 :* Call C_11 topSubDmd
--- | First argument of 'GHC.Exts.atomically#': @MCM(U)@.
+-- | First argument of 'GHC.Exts.atomically#': @CM(U)@.
-- Called at least once, possibly many times.
strictManyApply1Dmd :: Demand
strictManyApply1Dmd = C_1N :* Call C_1N topSubDmd
--- | First argument of catch#: @1C1(U)@.
+-- | First argument of catch#: @1CS(U)@.
-- Evaluates its arg lazily, but then applies it exactly once to one argument.
lazyApply1Dmd :: Demand
lazyApply1Dmd = C_01 :* Call C_01 topSubDmd
--- | Second argument of catch#: @1C1(CS(U))@.
--- Calls its arg lazily, but then applies it exactly once to an additional argument.
+-- | Second argument of catch#: @1CS(CS(U))@.
+-- Evaluates its arg lazily, but then applies it exactly once to two arguments.
lazyApply2Dmd :: Demand
-lazyApply2Dmd = C_01 :* Call C_01 (Call C_11 topSubDmd)
+lazyApply2Dmd = C_01 :* Call C_11 (Call C_11 topSubDmd)
-- | Make a 'Demand' evaluated at-most-once.
oneifyDmd :: Demand -> Demand
@@ -511,6 +516,14 @@ strictifyDictDmd ty (n :* Prod ds)
= Nothing
strictifyDictDmd _ dmd = dmd
+-- | Peels the evaluation cardinality of a 'Demand' and multiplies it with
+-- the relative parts of the 'SubDemand'. See Note [Absent sub-demand].
+peelDmd :: Demand -> SubDemand
+peelDmd (n :* sd)
+ | isAbs n = seqSubDmd
+ | isStrict n = sd
+ | otherwise = C_01 `multSubDmd` sd
+
-- | Wraps the 'SubDemand' with a one-shot call demand: @d@ -> @CS(d)@.
mkCallDmd :: SubDemand -> SubDemand
mkCallDmd sd = Call C_11 sd
@@ -568,7 +581,7 @@ argsOneShots (StrictSig (DmdType _ arg_ds _)) n_val_args
argOneShots :: Demand -- ^ depending on saturation
-> [OneShotInfo]
-- ^ See Note [Computing one-shot info]
-argOneShots (_ :* sd) = go sd -- See Note [Call demands are relative]
+argOneShots (n :* sd) = go (multSubDmd n sd) -- See Note [Call demands are relative]
where
go (Call n sd)
| isUsedOnce n = OneShotLam : go sd
@@ -605,9 +618,9 @@ In #7319 we get
Note [Call demands are relative]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The expression @if b then 0 else f 1 2 + f 3 4@ uses @f@ according to the demand
-@UCU(CS(P(U)))@, meaning
+@UCM(CS(P(U)))@, meaning
- "f is called multiple times or not at all (CU), but each time it
+ "f is called multiple times (CM) or not at all (U), but each time it
is called, it's called with *exactly one* (CS) more argument.
Whenever it is called with two arguments, we have no info on how often
the field of the product result is used (U)."
@@ -627,7 +640,7 @@ call site. Consider (#18903)
2 -> snd (g m)
_ -> uncurry (+) (g m)
-We want to give @g@ the demand @1C1(P(1P(U),SP(U)))@, so we see that in each call
+We want to give @g@ the demand @1CS(P(1P(U),SP(U)))@, so we see that in each call
site of @g@, we are strict in the second component of the returned pair.
This relative cardinality leads to an otherwise unexpected call to 'lubSubDmd'
@@ -639,6 +652,67 @@ is hurt and we can assume that the nested demand is 'botSubDmd'. That ensures
that @g@ above actually gets the @SP(U)@ demand on its second pair component,
rather than the lazy @1P(U)@ if we 'lub'bed with an absent demand.
+Note [Absent sub-demand]
+~~~~~~~~~~~~~~~~~~~~~~~~
+What is does the sub-demand of an absent demand tell us? There's no sense in
+telling *how deep* something was evaluated if it was not evaluated *at all*!
+Since the upper bound on evaluation cardinality is 0, we are free to choose
+whatever sub-demand we want.
+
+In case of 'lubDmd', we want that sub-demand to be 'botSubDmd', for similar
+reasons as we want 'botSubDmd' in Note [Call demands are relative]. Here's
+an example (T18885):
+
+ f :: Int -> Int
+ f y =
+ let x
+ | expensive y == 1 = (expensive (y+1), expensive (y+2))
+ | otherwise = (expensive (y+3), expensive (y+4))
+ in case () of
+ _ | expensive (y+5) == 42 -> fst x
+ _ | expensive (y+6) == 41 -> fst x + snd x
+ _ | otherwise -> 0
+
+Without the 'botSubDmd' special case, the demand on @x@ is 1P(1P(U),1P(U)).
+The outer evaluation cardinality is lazy and recursively makes all field
+demands lazy, too, so there's plenty of syntactic structure in our product
+demand language we leave unused.
+But note how everytime we evaluate @x@ to WHNF, we also evaluate its first
+pair component! We could say @1P(SP(U),1P(U))@ to encode that and the
+'botSubDmd' special case gives us exactly the means to infer that!
+
+We could then go on to exploit the nested strictness by transforming @x@ to
+
+ let x
+ | expensive y == 1 = case expensive (y+1) of !n -> (n, expensive (y+2))
+ | otherwise = case expensive (y+3) of !n -> (n, expensive (y+4))
+
+which would save allocation of a thunk. Unfortunately, we haven't written that
+transformation yet.
+
+But now we say (T18885b) that @test@ in
+
+ force :: (Int, Int) -> (Int, Int)
+ force p@(!x, !y) = p
+ {-# NOINLINE force #-}
+
+ test :: (Int, Int) -> Int -> (Int, Int)
+ test p z = case p of p'
+ (x, y) | odd z -> force p
+ | otherwise -> (1, 2)
+
+has strictness signature <SP(SP(U),SP(U))><SP(U)>, which is wrong! @test@
+is not strict in the fields of @p@. The problem is that the case binder @p'@
+has demand 1P(SP(U),SP(U)) from the call to @force@. When we apply the field
+demands to the alt binders ('addCaseBndrDmd'), we get strict demands on @x@
+and @y@, which is wrong.
+The solution is that we have to multiply the vase binders evaluation
+cardinality onto its field demands when we unwrap it! The field demands are
+relative to one evaluation, so similarly to dmdAnalStar the field demands
+have to lazified if the evaluation cardinality wasn't strict.
+(But we have to leave the usage alone, because it's already absolute.)
+Hence we do 'peelDmd' before calling 'addCaseBndrDmd'.
+
Demand on case-alternative binders]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The demand on a binder in a case alternative comes