
#14998: Sort out the strictness mess for exceptions
-------------------------------------+-------------------------------------
Reporter: simonpj | Owner: (none)
Type: bug | Status: new
Priority: normal | Milestone: 8.4.3
Component: Compiler | Version: 8.2.2
Keywords: | Operating System: Unknown/Multiple
Architecture: | Type of failure: None/Unknown
Unknown/Multiple |
Test Case: | Blocked By:
Blocking: | Related Tickets:
Differential Rev(s): | Wiki Page:
-------------------------------------+-------------------------------------
Over time we have evolved into a messy and bad place for the interaction
of strictness analysis and exceptions.
Here's the amazing history of the strictness of `catch#`
* Dec 13: 0558911f91c: catch# gets a strictness signature with
`lazyApply1Dmd`
* Jul 15: 7c0fff41789: catch# goes from `lazyApply1Dmd` to
`strictApply1Dmd`
* Dec 15: 28638dfe79e: catch# goes from `strictApply1Dmd` to
`lazyApply1Dmd`. Trac #10712
* Jan 16: 9915b656440: catch# goes from `lazyApply1Dmd` to `catchArgDmd`,
and result goes from `botRes` to `exnRes` Trac #11222.
* Mar 17: 701256df88c: catch# goes from 'catchArgDmd` to `lazyApply1Dmd`.
This ticket #13330.
See also
* `Note [Strictness for mask/unmask/catch]` in `primops.txt/pp`.
* `exceptions_and_strictness` in `primops.txt/pp`.
* `Note [Exceptions and strictness]` in `Demand.hs`
* #11555, #13330, #10712, #11222
'''Item 1: ThrowsExn'''
* The strictness analyser has quite a bit of complexity around
`ThrowsExn`.
* `ThrowsExn` only differs from `Diverges` if the `ExnStr` flag is set to
`ExnStr`
* And the only place that happens is in `Demand.catchArgDmd`
* The only place `catchArgDmd` is used is in `primops.txt.pp`.
Originally, in the patch for #11222, `catchArgDmd` was used for the first
arg of `catch#`, `catchSTM#` and `catchRetry#`.
* But the patch in this ticket removed two of those three; now only
`catchRetry#` uses the `catchArgDmd` thing.
It looks to me as if `catchRetry#` was left out by mistake; and indeed
David says "I left it out on the grounds that I didn't understand it well
enough."
And if so, that's the last use of `catchArgDmd` and I think that all the
tricky `ThrowsExn` machinery can disappear.
'''Item 2: strictness of catchException'''
As a result of all this to-and-fro we have in GHC.IO
{{{
catch (IO io) handler = IO $ catch# io handler'
catchException !io handler = catch io handler
}}}
That is, `catchException` is strict in the IO action itself. But Neil
argues in #11555, commment:6 that this is bad because it breaks the monad
laws.
I believe that there is some claimed performance gain from the strictness
of `catchException`, but I don't believe it exists. The perf gain was
from when it had a `strictApply1Dmd`; that is, it promised to call its
argument. See this note in `primops.txt.pp`
{{{
-- Note [Strictness for mask/unmask/catch]
-- Consider this example, which comes from GHC.IO.Handle.Internals:
-- wantReadableHandle3 f ma b st
-- = case ... of
-- DEFAULT -> case ma of MVar a -> ...
-- 0# -> maskAsynchExceptions# (\st -> case ma of MVar a ->
...)
-- The outer case just decides whether to mask exceptions, but we don't
want
-- thereby to hide the strictness in 'ma'! Hence the use of
strictApply1Dmd.
}}}
I think the above saga was all in pursuit of exposing the strictness of
`ma` if we used `catch#` instead of `maskAsynchExceptions#` in this
example. But the saga ultimate appears to have failed, and the strictenss
annotation in `catchException` is a vestige that carries no benefit, but
does requires comments etc.
'''Item 3: performance'''
If making `catch#` have strictness `strictApply1Dmd` improves perf, it
would be good to know where. That would entail making the (tiny) change,
recompiling all, and nofibbing. Here is the commit message in 7c0fff41789,
which added `strictApply1Dmd`:
{{{
commit 7c0fff41789669450b02dc1db7f5d7babba5dee6
Author: Simon Peyton Jones