Note [Merge Nested Cases]

GHC/Core/Utils.hs:837 compiler 1 ticket

       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

Referenced by 7