Lost opportunity to eliminate seq
Consider this:
f xs = let ys = reverse xs in
ys `seq` length xs +
length (reverse (case ys of { a:as -> as; [] -> [] }))
We end up with this
Foo.$wf
= \ (@ a_s4a0) (w_s4a1 :: [a_s4a0]) ->
(1) case GHC.List.reverse1 @ a_s4a0 w_s4a1 (GHC.Types.[] @ a_s4a0)
of ys_ap9 { __DEFAULT ->
case GHC.List.$wlenAcc @ a_s4a0 w_s4a1 0#
of ww2_a49t { __DEFAULT ->
(2) case ys_ap9 of {
[] ->
case Foo.f1 @ a_s4a0 of { GHC.Types.I# v1_B2 ->
GHC.Prim.+# ww2_a49t v1_B2
};
: a1_aK8 as_aK9 ->
case GHC.List.$wlenAcc
@ a_s4a0
(GHC.List.reverse1 @ a_s4a0 as_aK9 (GHC.Types.[] @ a_s4a0))
0#
of ww1_X49V
{ __DEFAULT ->
GHC.Prim.+# ww2_a49t ww1_X49V
} } } }
The case expression (1) is the seq
on ys. The case marked (2) is the
case analysis in the argument of the second reverse
.
But that first seq
is redundant! We could equally well say
Foo.$wf
= \ (@ a_s4a0) (w_s4a1 :: [a_s4a0]) ->
case GHC.List.$wlenAcc @ a_s4a0 w_s4a1 0#
of ww2_a49t { __DEFAULT ->
(2) case GHC.List.reverse1 @ a_s4a0 w_s4a1 (GHC.Types.[] @ a_s4a0) of {
[] ->
case Foo.f1 @ a_s4a0 of { GHC.Types.I# v1_B2 ->
GHC.Prim.+# ww2_a49t v1_B2
};
: a1_aK8 as_aK9 ->
case GHC.List.$wlenAcc
@ a_s4a0
(GHC.List.reverse1 @ a_s4a0 as_aK9 (GHC.Types.[] @ a_s4a0))
0#
of ww1_X49V
{ __DEFAULT ->
GHC.Prim.+# ww2_a49t ww1_X49V
} } } }
That's better because we generate code for only two evals rather than three.
The general pattern is
case <scrut> of b { DEFAULT -> <body> }
==>
let b = <scrut> in <body>
where the case binder b
is used strictly in <body>
. In this case it's
safe to switch to a let
(marked as strict) which can now be inlined
or floated in a way that case
expressions cannot.
We already do this transformation, here in Simplify.rebuildCase
:
rebuildCase env scrut case_bndr alts@[(_, bndrs, rhs)] cont
...
| all_dead_bndrs
, if isUnliftedType (idType case_bndr)
then exprOkForSpeculation scrut
else exprIsHNF scrut || scrut_is_demanded_var scrut
= ...turn case into let...
The key bit (for this situation) is scrut_is_demanded_var
.
But it only fires if <scrut>
is a variable.
I see no reason for this restriction. I think it's sound
regardless. Yes, if we decide to inline the binding b = <scrut>
we
might change which exception appears; but that is within the semantics
of exceptions; and it's still true if <scrut>
is a variable.
So I think we can safely replace scrut_is_demand_var
with just
case_bndr_is_demanded
, independent of what scrut
looks like.
I dug back into ghc history to see how the current code came about, bu it had a long evolution and I didn't find any reason for sticking to a variable here.
Trac metadata
Trac field | Value |
---|---|
Version | 8.4.3 |
Type | Bug |
TypeOfFailure | OtherFailure |
Priority | normal |
Resolution | Unresolved |
Component | Compiler |
Test case | |
Differential revisions | |
BlockedBy | |
Related | |
Blocking | |
CC | |
Operating system | |
Architecture |