[GHC] #13227: Loss of full laziness in mapFB

GHC ghc-devs at haskell.org
Fri Feb 3 10:02:13 UTC 2017


#13227: Loss of full laziness in mapFB
-------------------------------------+-------------------------------------
        Reporter:  simonpj           |                Owner:  nomeata
            Type:  bug               |               Status:  patch
        Priority:  normal            |            Milestone:
       Component:  Compiler          |              Version:  8.0.1
      Resolution:                    |             Keywords:
Operating System:  Unknown/Multiple  |         Architecture:
                                     |  Unknown/Multiple
 Type of failure:  None/Unknown      |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):  Phab:D3067
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by simonpj):

 Joachim, I have resurrected from my hind-brain what was, perhaps, the
 original issue.  Consider something like
 {{{
 recover (f (\x. blah)) bloo
 }}}
 where `recover` :: (Exception -> IO a) -> IO a -> IO a` is some kind of
 exception handling.  And `f` is something like
 {{{
 f :: (Exception -> a) -> Exception -> IO a
 f g exn = return (g exn)
 }}}
 Now, is the `\x` one-shot or not?  Well, if `f` is applied to three args
 (the exception and the state token, then it'll call `g` exactly once`, so
 yes the `\x` will be one shot.  And hence there is no point floating a
 thunk out of `blah`.  Indeed doing so is positively harmful because it
 adds allocation before calling `recover`, and that allocation is only used
 on the cold (exception-handling) path.

 And yet with your change, we won't recognise that, because the call to `f`
 is not syntactically saturated.

 Moreover, the occurrence analyser is the '''only''' place where one-shot
 info is added to lambdas.  See your patch in #11770.  (I'd like this fact
 to be more clearly called out... I had to re-discover it today.  It is
 stated in `Note [Use one-shot information]` but not very loudly.)

 So we might ask

 1. Does this matter? Does it actually occur in practice? Your measurements
 suggest not.
 2. Would be easy to fix?  I think it might be.

 Concerning (2), look at the call to `argsOneShots` in `occAnalApp`.  We
 pass `n_val_args` to `argsOneShots`.  Consider something like
 {{{
 f (g (\y.e))
 }}}
 and suppose

 * `f` has a strictness sigature like `C1(L)`, saying that it calls its
 argument at most once
 * `g` has a strictness signature like `C1(L)L`, saying that when applied
 to two args it calls its first arg at most once.

 Then when we are doing `occAnalApp` for `g (\y.e)` we will have
 `[NoOneShot]` in the `occ_one_shots` field of `OccEnv`... that comes from
 `f` which guarantees to call its arg once.

 So I think we can just add the length of `occ_one_shots` to `n_val_args`
 before passing to `argsOneShots`.  And that is so easy it'd be worth
 doing.

 Here's a concrete test case
 {{{
 w :: (Int -> a) -> a
 w g = g 1
 {-# NOINLINE w #-}

 f :: (Int -> Bool) -> a -> [Bool]
 f g _ = [g 1, True, False]
 {-# NOINLINE f #-}

 h xs = w (f (\y -> null (reverse xs)))
 }}}
 You'll see that `null (reverse xs)` is floated out -- and it should not
 be.

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


More information about the ghc-tickets mailing list