[GHC] #9661: Branchless ==# is compiled to branchy code

GHC ghc-devs at haskell.org
Fri Oct 3 09:51:40 UTC 2014


#9661: Branchless ==# is compiled to branchy code
-------------------------------------+-------------------------------------
              Reporter:  dfeuer      |            Owner:
                  Type:  bug         |           Status:  new
              Priority:  normal      |        Milestone:
             Component:  Compiler    |          Version:  7.9
            Resolution:              |         Keywords:
      Operating System:              |     Architecture:  Unknown/Multiple
  Unknown/Multiple                   |       Difficulty:  Unknown
       Type of failure:  Runtime     |       Blocked By:
  performance bug                    |  Related Tickets:
             Test Case:              |
              Blocking:              |
Differential Revisions:              |
-------------------------------------+-------------------------------------

Comment (by dfeuer):

 Replying to [comment:1 jstolarek]:
 > After some thinking I don't think it's constant folding but more
 generally the special rules for `==#`. As promised here are some hints how
 to get started on this one:
 >
 > 1. Try to reproduce the problem with a simpler example, eg. using only
 two comparison primops and `orI#`. Try out different combinations of
 comparison primops. See also whether this happens for `andI#`. We want to
 be sure that this only depends on presence of `==#` and not other
 operators (don't forget `/=#`).

 This happens with both `==#` and `/=#`. It does not appear to happen with
 the others. Furthermore, it appears to happen only when one of the
 arguments is known at compile time.

 > 2. Once you have a smaller test case follow the transformation that GHC
 is performing to arrive at the result. Is it turning the simpler test case
 into the final result in one step or are there any intermediate steps?

 There are intermediate steps, but it starts very early. For example,

 {{{#!hs
 --Desugar
 equals0AndEquals2
 equals0AndEquals2 =
   \ x_a1wL y_a1wM -> andI# (==# x_a1wL 0) (==# y_a1wM 2)

 == Gentle simplification ==>

 equals0AndEquals2
 equals0AndEquals2 =
   \ x_a1wL y_a1wM ->
     andI#
       (case x_a1wL of _ {
          __DEFAULT -> 0;
          0 -> 1
        })
       (case y_a1wM of _ {
          __DEFAULT -> 0;
          2 -> 1
        })

 -- Nothing changes for a bit ....
 == Simplifier phase 2 ==>

 equals0AndEquals2
 equals0AndEquals2 =
   \ x_a1wL y_a1wM ->
     case x_a1wL of _ {
       __DEFAULT -> 0;
       0 ->
         case y_a1wM of _ {
           __DEFAULT -> 0;
           2 -> 1
         }
     }

 > 3. My guess would be that the culprit is `litEq` function in
 `PrelRules.lhs` module. The comment above it claims to do exactly what
 you've reported here. However, I'm not sure what the fix should be.
 Disabling this is probably not a good idea, since in some cases we really
 want that transformation to happen. We'd need to think more but first
 let's confirm whether `litEq` is the cause or not. You can verify that by
 disabling that rule and seeing whether the code compiles without branches.

 I'm confident you're right, but I won't have time to do that experiment
 until after Yom Kippur. The comment says

 {{{
 -- This is a Good Thing, because it allows case-of case things
 -- to happen, and case-default absorption to happen.  For
 -- example:
 --
 --      if (n ==# 3#) || (n ==# 4#) then e1 else e2
 -- will transform to
 --      case n of
 --        3# -> e1
 --        4# -> e1
 --        m  -> e2
 }}}

 There are a couple `tagToEnum#` invocations missing there, but let's look
 at the whole process. We're presumably starting with

 {{{#!hs
 if (n == 3) || (n == 4) then e1 else e2
 }}}

 Expanding `if`,

 {{{#!hs
 case (n == 3) || (n == 4) of
   True -> e1
   False -> e2
 }}}

 Expanding `(||)`,

 {{{#!hs
 case (case n == 3 of {True -> True; False -> n == 4}) of
   True -> e1
   False -> e2
 }}}

 Case of case gets us

 {{{#!hs
 case n == 3 of
   True -> case True of {True -> e1; False -> e2}
   False -> case n == 4 of {True -> e1; False -> e2}
 }}}

 Case of known constructor or whatever it's called produces

 {{{#!hs
 case n == 3 of
   True -> e1
   False -> case n == 4 of
              True -> e1
              False -> e2
 }}}

 So we can get this far without knowing anything about what `(==)` means.
 Now let's add part of that knowledge:
 {{{#!hs
 case n# ==# 3# of
   1# -> e1
   0# -> case n# ==# 4# of
           1# -> e1
           0# -> e2
 }}}

 In this case, this is probably just fine, but I imagine the example is
 intended to suggest that there are (perhaps many) more tests, so that a
 different pattern matching strategy may be in order. I would venture to
 guess that what we want to do is attack it at this stage—recognizing that
 we're looking at nested case of `==#` with a shared operand.

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


More information about the ghc-tickets mailing list