Note [Merge Nested Cases]
case e of b { ==> case e of b {
p1 -> rhs1 p1 -> rhs1
... ...
pm -> rhsm pm -> rhsm
_ -> case b of b' { pn -> let b'=b in rhsn
pn -> rhsn ...
... po -> let b'=b in rhso
po -> rhso _ -> let b'=b in rhsd
_ -> rhsd
}
which merges two cases in one case when -- the default alternative of
the outer case scrutinises the same variable as the outer case. This
transformation is called Case Merging. It avoids that the same
variable is scrutinised multiple times.
Wrinkles
(MC1) Historical note. I tried making `mergeCaseAlts` "looks though" an inner
single-alternative case-on-variable. For example
case x of {
...outer-alts...
DEFAULT -> case y of (a,b) ->
case x of { A -> rhs1; B -> rhs2 }
===>
case x of
...outer-alts...
a -> case y of (a,b) -> rhs1
B -> case y of (a,b) -> rhs2
This duplicates the `case y` but it removes the case x; so it is a win
in terms of execution time (combining the cases on x) at the cost of
perhaps duplicating the `case y`. A case in point is integerEq, which
is defined thus
integerEq :: Integer -> Integer -> Bool
integerEq !x !y = isTrue# (integerEq# x y)
which becomes
integerEq
= \ (x :: Integer) (y_aAL :: Integer) ->
case x of x1 { __DEFAULT ->
case y of y1 { __DEFAULT ->
case x1 of {
IS x2 -> case y1 of {
__DEFAULT -> GHC.Types.False;
IS y2 -> tagToEnum# @Bool (==# x2 y2) };
IP x2 -> ...
IN x2 -> ...
We want to merge the outer `case x` with the inner `case x1`.
But (a) this is all a bit dubious: see #24251, and
(b) it is hard to combine with (MC4)
So I'm not doing this any more. If we want to do it, we'll handle it
separately: #24251.
End of historical note
(MC2) The auxiliary bindings b'=b are annoying, because they force another
simplifier pass, but there seems no easy way to avoid them. See
Note [Which transformations are innocuous] in GHC.Core.Opt.Stats.
(MC3) Consider
case f x of (r::Int#) -> tagToEnum# r :: Bool
`mergeCaseAlts` as a special case to treat this as if it was
case f x of r ->
case r of { 0# -> False; 1# -> True }
which can be merged to
case f x of { 0# -> False; 1# -> True }
To see why this is important, return to
case f x of (r::Int#) -> tagToEnum# r :: Bool
and supppose `f` inlines to a case expression. Then then we get
let $j r = tagToEnum# r
case .. of { .. jump $j 0#; ...jump $j 1# ... }
Now if the entire expression is consumed by another case-expression,
that outer case will only see (tagToEnum# r) which it can't do much
with. Whereas the result of the above case-merge generates much better
code: no branching on Int#
(MC4) Consider
case f x of r ->
join $j y = <rhs> in
case r of { ...alts ... }
This is pretty common, and it a pity for it to defeat the case-merge
transformation; and it makes the optimiser fragile to inlining decisions
for join points.
So `mergeCaseAlts` floats out any join points. It doesn't float out
non-join-points unless the /outer/ case has just one alternative; doing
so would risk more allocation
(MC5) See Note [Cascading case merge]
See also Note [Example of case-merging and caseRules] in GHC.Core.Opt.Simplify.Utils References 3
- Example of case-merging and caseRules GHC.Core.Opt.Simplify.Utils
- Which transformations are innocuous GHC.Core.Opt.Stats
- Cascading case merge GHC.Core.Utils
Referenced by 7
- GHC.Core.Utils call site ×4
- Merging nested cases GHC.Core.Opt.Simplify.Utils
- Example of case-merging and caseRules GHC.Core.Opt.Simplify.Utils
- GHC.Core.Opt.Simplify.Utils call site