[GHC] #15631: Lost opportunity to eliminate seq
GHC
ghc-devs at haskell.org
Wed Sep 12 08:46:53 UTC 2018
#15631: Lost opportunity to eliminate seq
-------------------------------------+-------------------------------------
Reporter: simonpj | Owner: (none)
Type: bug | Status: new
Priority: normal | Milestone: 8.6.1
Component: Compiler | Version: 8.4.3
Keywords: | Operating System: Unknown/Multiple
Architecture: | Type of failure: None/Unknown
Unknown/Multiple |
Test Case: | Blocked By:
Blocking: | Related Tickets:
Differential Rev(s): | Wiki Page:
-------------------------------------+-------------------------------------
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.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/15631>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list