Skip to content

DmdAnal: `dmdAnalStar` could yield better usage for trivial args and bindings

Consider

f :: (Bool, Bool) -> (Bool, Bool)
f pr = (pr `seq` True, case pr of (a,b) -> a && b)
{-# NOINLINE f #-}
-- idDmdSig f = LP(1L,1L)

g :: (Bool, Bool) -> ()
g pr = f pr `seq` ()
-- idDmdSig g = L

g simply calls f, yet g puts a worse demand on pr than f; it loses the information that pr's components are only evaluated once.

That is due to dmdAnalStar e (n :* sd), which is used when analysing the argument pr in the call f pr in demand LP(1L,1L). That will

  1. Call dmdAnal "pr" "P(1L,1L)", as if pr was evaluated exactly once in sub-demand P(1L,1L). We get back [pr |-> 1P(1L,1L)] (which of course is too optimistic).
  2. Then it worsens the resulting demand type to account for n=L, by multiplying it with L via multDmdType. That ultimately calls multDmd "L" on the demand of x: multDmd L 1P(1L,1L) === LP(LL,LL) === L.

It's alright to turn the outer 1P(..) into LP(..), but we don't actually need to worsen the inner P(1L,1L), because it is the exact demand that is put on the argument already.

NB: This ticket is specifically concerned with upper bounds. The multDmd "L" situation (with a used more than once upper boudn) only occurs when an arg pr is trivial (or if the RHS of a bindings is trivial). Otherwise, dmdTransformThunkDmd anticipates the memoisation of complex expressions and call oneifyDmd instead. So if we had a call like f (x, True) instead, then we'd get dmdAnalStar "(x, True)" "1P(1L,1L)", e.g., with the oneified demand 1P(..) and all will be well, as we ultimately get a demand of 1L on x.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information