[GHC] #2940: Do CSE after CorePrep

GHC ghc-devs at haskell.org
Sat Nov 28 21:51:11 UTC 2015


#2940: Do CSE after CorePrep
-------------------------------------+-------------------------------------
        Reporter:  simonpj           |                Owner:  simonpj
            Type:  bug               |               Status:  new
        Priority:  lowest            |            Milestone:  8.0.1
       Component:  Compiler          |              Version:  6.10.1
      Resolution:                    |             Keywords:
Operating System:  Unknown/Multiple  |         Architecture:
 Type of failure:  Runtime           |  Unknown/Multiple
  performance bug                    |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by osa1):

 Simon, I was experimenting with CSE and unless I'm missing something I
 think
 current CSE is basically useless and we can just remove it.

 As far as I understand from the implementation, current CSE implementation
 never introduces a let binding. So for example if I have something like:

 {{{#!haskell
 g x = (x + 1, x + 1)
 }}}

 Current implementation never does CSE here. What's worse is that even if I
 have
 a let binding like this:

 {{{#!haskell
 g x = let x_1 = x + 1
        in (x_1, x + 1)
 }}}

 Most of the time CSE can't do anything, because some other pass
 inlines/unfolds
 `x_1` here(even with -O0). `g` above is compiled to this:

 {{{#!haskell
 g :: Int -> (Int, Int)
 g =
   \ (x_atS :: Int) ->
     (case x_atS of _ { I# x_a1IL -> I# (+# x_a1IL 1#) },
      case x_atS of _ { I# x_a1IL -> I# (+# x_a1IL 1#) })
 }}}

 See how let binding is removed. CSE can't do anything here. Funny thing is
 even
 if I mark this let binding as `NOINLINE` CSE can't work, because of Note
 [CSE
 for INLINE and NOINLINE]. This is what happens when I mark `x_1` as
 `NOINLINE`:

 {{{#!haskell
 g' :: Int -> (Int, Int)
 g' =
   \ (x_av2 :: Int) ->
     let {
       x_1_s1Jd :: Int
       x_1_s1Jd = case x_av2 of _ { I# x_a1IL -> I# (+# x_a1IL 1#) } } in
     (case x_av2 of _ { I# x_a1IL -> I# (+# x_a1IL 1#) }, x_1_s1Jd)
 }}}

 There's a clear CSE opportunity missed because of Note [CSE for INLINE and
 NOINLINE].


 So unless we actually run this after `CorePrep` it just can't do anything.

 I tried on a couple of examples and it seems like current CSE
 implementation
 actually maintains invariants at least some of the time. For example, it
 works
 find on the code in the code above. Do we have something like `CoreLint`
 but
 fore `CorePrep` invariants?

 ----

 In the examples I tried CSE never worked before the `CorePrep` step. After
 `CorePrep` it worked some of the time. For example, `g` above is compiled
 to
 this:

 {{{#!haskell
 -- RHS size: {terms: 17, types: 8, coercions: 0}
 g :: Int -> (Int, Int)
 g =
   \ (x_s2nV :: Int) ->
     let {
       sat_s2o3 :: Int
       sat_s2o3 =
         case x_s2nV of wild_s2o0 { I# x1_s2o1 ->
         case +# x1_s2o1 1# of sat_s2o2 { __DEFAULT -> I# sat_s2o2 }
         } } in
     let {
       sat_s2nZ :: Int
       sat_s2nZ = sat_s2o3 } in
     (sat_s2o3, sat_s2o3)
 }}}

 `h` also CSEs nicely if we run it after `CorePrep`:

 {{{#!haskell
 -- RHS size: {terms: 23, types: 8, coercions: 0}
 h :: Int# -> (Int, Int)
 h =
   \ (x_s2nq :: Int#) ->
     case +# x_s2nq 1# of sat_s2nt { __DEFAULT ->
     case *# sat_s2nt 2# of sat_s2nu { __DEFAULT ->
     let {
       sat_s2nv :: Int
       sat_s2nv = I# sat_s2nu } in
     case sat_s2nt of sat_s2nr { __DEFAULT ->
     let {
       sat_s2ns :: Int
       sat_s2ns = I# sat_s2nt } in
     (sat_s2ns, sat_s2nv)
     }
     }
     }
 }}}

 It doesn't work on `rev`, because of how `CorePrep` generates a program
 without
 any CSE opportunity:

 {{{#!haskell
 -- RHS size: {terms: 23, types: 19, coercions: 0}
 Main.rev :: [GHC.Types.Bool] -> [GHC.Types.Bool]
 Main.rev =
   \ (xs_s2nl :: [GHC.Types.Bool]) ->
     let {
       sat_s2np :: [GHC.Types.Bool]
       sat_s2np =
         case GHC.List.reverse1
                @ GHC.Types.Bool xs_s2nl (GHC.Types.[] @ GHC.Types.Bool)
         of sat_s2no { __DEFAULT ->
         GHC.List.filter
           @ GHC.Types.Bool (GHC.Base.id @ GHC.Types.Bool) sat_s2no
         } } in
     case GHC.List.reverse1
            @ GHC.Types.Bool xs_s2nl (GHC.Types.[] @ GHC.Types.Bool)
     of sat_s2nm { __DEFAULT ->
     case GHC.List.reverse1
            @ GHC.Types.Bool sat_s2nm (GHC.Types.[] @ GHC.Types.Bool)
     of sat_s2nn { __DEFAULT ->
     GHC.Base.++ @ GHC.Types.Bool sat_s2nn sat_s2np
     }
     }
 }}}

 Ideally we'd like to generate a binding for `GHC.List.reverse1 @
 GHC.Types.Bool xs_s2nl (GHC.Types.[] @ GHC.Types.Bool)` but since this
 expression is not bound to a let-binder we can't CSE.

 (This would work if had the invariant of having only variables(and
 literals) in
 scrutinee position)

 For the record, this is what's generated by CSE after `CorePrep`:

 {{{#!haskell
 rev :: [Bool] -> [Bool]
 rev =
   \ (xs_s2nl :: [Bool]) ->
     let {
       sat_s2np :: [Bool]
       sat_s2np =
         case reverse1 @ Bool xs_s2nl ([] @ Bool) of sat_s2no { __DEFAULT
 ->
         filter @ Bool (id @ Bool) sat_s2no
         } } in
     case reverse1 @ Bool xs_s2nl ([] @ Bool) of sat_s2nm { __DEFAULT ->
     case reverse1 @ Bool sat_s2nm ([] @ Bool)
     of sat_s2nn { __DEFAULT ->
     ++ @ Bool sat_s2nn sat_s2np
     }
     }
 }}}

 ----

 I want to work on running CSE after CorePrep, but before that I'm planning
 implementing a CoreLint-like pass for checking CorePrep invariants. Simon,
 does
 that make sense? Any ideas? Am I missing anything in my observations?

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/2940#comment:17>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list