Skip to content

Drop redundant evals, perhaps

Consider this code

f xs = xs `seq`
       (let {-# NOINLINE t #-}
            t = reverse xs in
        case xs of
          [] ->  (t,True)
          (_:_) -> (t,False))

We get this Core:

f = \ (@a_ayt) (xs_ah4 :: [a_ayt]) ->
      case xs_ah4 of xs1_X0 { __DEFAULT ->              <------------- Redundant eval
      let {
        t_sz4 [InlPrag=NOINLINE] :: [a_ayt]
        [LclId]
        t_sz4
          = GHC.Internal.List.reverse1
              @a_ayt xs1_X0 (GHC.Types.[] @a_ayt) } in
      case xs1_X0 of {
        [] -> (t_sz4, GHC.Types.True);
        : ds_dyY ds1_dyZ -> (t_sz4, GHC.Types.False)
      }
      }

Notice that we evaluate xs and then (after the let) case-analyse it. We used to drop the first eval, on the grounds that xs was evaluated "later", but we stopped doing that in #24251 (closed), for good reason.

If the let wasn't in the way we'd have

      case xs_ah4 of xs1_X0 { __DEFAULT ->              <------------- Redundant eval
      case xs1_X0 of {
        [] -> (t_sz4, GHC.Types.True);
        : ds_dyY ds1_dyZ -> (t_sz4, GHC.Types.False)

and "case-merging" combines the two case expressions into one; see Note [Merge Nested Cases] and mergeCaseAlts. But in the main example there is a let in the way.

This ticket asks the question Should we seek to eliminate the redundant eval?. The MR !12239 shows one way to do so, in the case where xs is evaluated "soon". (See the MR for details.)

It's not obvious that is worth doing at all -- see discussion on !12239. Doing so doesn't save much (see the MR), and it might change evaluation order, which in turn might have consequences for space behaviour. And yet worker/wrapper also changes space behaviour.

This ticket is a place to discuss the issue.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information