Case of known constructor can weaken indicated demands on a functions arguments.
We start out with a function like this.
$wfoo x'[Dmd=1S]) _[Dmd=L] =
let box[Dmd=1S(1S,L)] = (x', _)
in
...
bar box
=> (inlining) We inline bar which introduces a branch:
$wfoo x'[Dmd=SP]) _[Dmd=L] =
let box[Dmd=1S(1S,L)] = (x', _)
in
...
case z of
C1 -> baz box
C2 -> baz (case box of (x'',_) -> (x'',sth))
=> Known Con ends up eliminating box
on one of the two branches.
-- Different demand sigs
$wfoo x'[Dmd=SP]) _[Dmd=L] =
let box[Dmd=L] = (x', _)
in
...
case z of
C1 -> baz box -- x' only demanded via box on this branch
C2 -> baz (x',sth) -- box dead on this branch!
A few confusing things happen here:
-
box
is no longer strict which might or might not matter, but it seems we update the demand on it correctly. -
x'
s demand is not updated. So it's currently still given a strict demand. But if we rerun the demand analysier it will be given aL
demand. - The behaviour of the RHS in regards to it's arguments has not changed. Although the demand we will infer in future demand analysier runs has.
This seems rather rare but I had this come up with a patch of mine where I had assumed strictness on arguments for a RHS never goes down (which I think also isn't true).
And here is a full reproducer where this happens. Compile with -ddump-str-anal and look at the signatures for foo
.
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE BangPatterns #-}
{-# OPTIONS_GHC -O -fno-worker-wrapper -fno-cpr-anal -fno-full-laziness -O -ddump-stranal -flate-dmd-anal -fstrictness-before=1 -fno-float-in#-}
{-# LANGUAGE PartialTypeSignatures #-}
module A where
import GHC.Exts
data Env = Env Bool Bool {-# NOUNPACK #-} Int Int Char
data MyEnum = A | B | C | D
{-# NOINLINE doSomething #-}
doSomething :: Env -> Bool
doSomething (Env a b _ _ _ ) = a || b
{-# NOINLINE doSomething2 #-}
doSomething2 :: Env -> Bool
doSomething2 (Env a b _ _ _) = a || b
{-# INLINE[0] bar #-}
bar :: MyEnum -> Env -> Bool
bar b env =
case b of
A -> (doSomething) env
B -> (doSomething2) env
C -> (doSomething) (case env of Env x y _ _ _ -> x `seq` (Env x False 1 2 '1'))
D -> doSomething env || doSomething2 env
foo :: Bool -> Bool -> A.MyEnum -> A.MyEnum -> Int -> Char -> Bool
foo x y b c n char =
let env =
Env x (n == 42 ) (n) 4 $ (succ $ succ $ succ $ succ $ succ char)
in case c of
!_ -> bar b env
The good thing though is I can't think of a way how this could cause issues at the moment.
-
box
/env
needs to be in WHNF in order for known constructor to trigger. So dropping a seq is fine either way. If they are a thunk I don't think we can get into this situation. But it's possible I'm wrong on that. - A function changing from showing a strict demand for an argument at first, and a lazy demand later is odd, but here the function remains strict in it's behavior. It's just that dmdAnal no longer can recognize it as such in future runs.
It can certainly make it more confusing to look at core though! And maybe it is dangerous and I just missed how so. Hence the ticket.