Note [Case-to-let for strictly-used binders]
If we have this:
case <scrut> of r { _ -> ..r.. }
where 'r' is used strictly in (..r..), we /could/ safely transform to
let r = <scrut> in ...r...
As a special case, we have a plain `seq` like
case r of r1 { _ -> ...r1... }
where `r` is used strictly, we /could/ simply drop the `case` to get
...r....
HOWEVER, there are some serious downsides to this transformation, so
GHC doesn't do it any longer (#24251):
* Suppose the Simplifier sees
case x of y* { __DEFAULT ->
let z = case y of { __DEFAULT -> expr } in
z+1 }
The "y*" means "y is used strictly in its scope. Now we may:
- Eliminate the inner case because `y` is evaluated.
Now the demand-info on `y` is not right, because `y` is no longer used
strictly in its scope. But it is hard to spot that without doing a new
demand analysis. So there is a danger that we will subsequently:
- Eliminate the outer case because `y` is used strictly
Yikes! We can't eliminate both!
* It introduces space leaks (#24251). Consider
go 0 where go x = x `seq` go (x + 1)
It is an infinite loop, true, but it should not leak space. Yet if we drop
the `seq`, it will. Another great example is #21741.
* Dropping the outer `case` can change the error behaviour. For example,
we might transform
case x of { _ -> error "bad" } --> error "bad"
which is might be puzzling if 'x' currently lambda-bound, but later gets
let-bound to (error "good"). Tht is OK accoring to the paper "A semantics for
imprecise exceptions", but see #8900 for an example where the loss of this
transformation bit us in practice.
* If we have (case e of x -> f x), where `f` is strict, then it looks as if `x`
is strictly used, and we could soundly transform to
let x = e in f x
But if f's strictness info got worse (which can happen in in obscure cases;
see #21392) then we might have turned a non-thunk into a thunk! Bad.
Lacking this "drop-strictly-used-seq" transformation means we can end up with
some redundant-looking evals. For example, consider
f x y = case x of DEFAULT -> -- A redundant-looking eval
case y of
True -> case x of { Nothing -> False; Just z -> z }
False -> case x of { Nothing -> True; Just z -> z }
That outer eval will be retained right through to code generation. But,
perhaps surprisingly, that is probably a /good/ thing:
Key point: those inner (case x) expressions will be compiled a simple 'if',
because the code generator can see that `x` is, at those points, evaluated
and properly tagged.
If we dropped the outer eval, both the inner (case x) expressions would need to
do a proper eval, pushing a return address, with an info table. See the example
in #15631 where, in the Description, the (case ys) will be a simple multi-way
jump.
In fact (#24251), when I stopped GHC implementing the drop-strictly-used-seqs
transformation, binary sizes fell by 1%, and a few programs actually allocated
less and ran faster. A case in point is nofib/imaginary/digits-of-e2. (I'm not
sure exactly why it improves so much, though.)
Slightly related: Note [Empty case alternatives] in GHC.Core.
Historical notes:
There have been various earlier versions of this patch:
* By Sept 18 the code looked like this:
|| scrut_is_demanded_var scrut
scrut_is_demanded_var :: CoreExpr -> Bool
scrut_is_demanded_var (Cast s _) = scrut_is_demanded_var s
scrut_is_demanded_var (Var _) = isStrUsedDmd (idDemandInfo case_bndr)
scrut_is_demanded_var _ = False
This only fired if the scrutinee was a /variable/, which seems
an unnecessary restriction. So in #15631 I relaxed it to allow
arbitrary scrutinees. Less code, less to explain -- but the change
had 0.00% effect on nofib.
* Previously, in Jan 13 the code looked like this:
|| case_bndr_evald_next rhs
case_bndr_evald_next :: CoreExpr -> Bool
See Note [Case binder next]
case_bndr_evald_next (Var v) = v == case_bndr
case_bndr_evald_next (Cast e _) = case_bndr_evald_next e
case_bndr_evald_next (App e _) = case_bndr_evald_next e
case_bndr_evald_next (Case e _ _ _) = case_bndr_evald_next e
case_bndr_evald_next _ = False
This patch was part of fixing #7542. See also
Note [Eta reduction soundness], criterion (E) in GHC.Core.Utils.)
Further notes about case elimination
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Consider: test :: Integer -> IO ()
test = print
Turns out that this compiles to:
Print.test
= \ eta :: Integer
eta1 :: Void# ->
case PrelNum.< eta PrelNum.zeroInteger of wild { __DEFAULT ->
case hPutStr stdout
(PrelNum.jtos eta ($w[] @ Char))
eta1
of wild1 { (# new_s, a4 #) -> PrelIO.lvl23 new_s }}
Notice the strange '<' which has no effect at all. This is a funny one.
It started like this:
f x y = if x < 0 then jtos x
else if y==0 then "" else jtos x
At a particular call site we have (f v 1). So we inline to get
if v < 0 then jtos x
else if 1==0 then "" else jtos x
Now simplify the 1==0 conditional:
if v<0 then jtos v else jtos v
Now common-up the two branches of the case:
case (v<0) of DEFAULT -> jtos v
Why don't we drop the case? Because it's strict in v. It's technically
wrong to drop even unnecessary evaluations, and in practice they
may be a result of 'seq' so we *definitely* don't want to drop those.
I don't really know how to improve this situation. References 2
- Eta reduction soundness GHC.Core.Opt.Arity
- Empty case alternatives GHC.Core
Referenced by 2
- Case to let transformation GHC.Core.Opt.Simplify.Iteration
- GHC.Core.Opt.Simplify.Iteration call site