[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