Note [Precise exceptions and strictness analysis]
We have to take care to preserve precise exception semantics in strictness analysis (#17676). There are two scenarios that need careful treatment. The fixes were discussed at https://gitlab.haskell.org/ghc/ghc/wikis/fixing-precise-exceptions Recall that raiseIO# raises a *precise* exception, in contrast to raise# which raises an *imprecise* exception. See Note [Precise vs imprecise exceptions]. Scenario 1: Precise exceptions in case alternatives Unlike raise# (which returns botDiv), we want raiseIO# to return exnDiv. Here's why. Consider this example from #13380 (similarly #17676): f x y | x>0 = raiseIO# Exc | y>0 = return 1 | otherwise = return 2 Is 'f' strict in 'y'? One might be tempted to say yes! But that plays fast and loose with the precise exception; after optimisation, (f 42 (error "boom")) turns from throwing the precise Exc to throwing the imprecise user error "boom". So, the defaultFvDmd of raiseIO# should be lazy (topDmd), which can be achieved by giving it divergence exnDiv. See Note [Default demand on free variables and arguments]. Why don't we just give it topDiv instead of introducing exnDiv? Because then the simplifier will fail to discard raiseIO#'s continuation in case raiseIO# x s of { (# s', r #) -> <BIG> } which we'd like to optimise to case raiseIO# x s of {} Hence we came up with exnDiv. The default FV demand of exnDiv is lazy (and its default arg dmd is absent), but otherwise (in terms of 'isDeadEndDiv') it behaves exactly as botDiv, so that dead code elimination works as expected. This is tracked by T13380b. Scenario 2: Precise exceptions in case scrutinees Consider (more complete examples in #148, #1592, testcase strun003) case foo x s of { (# s', r #) -> y } Is this strict in 'y'? Often not! If @foo x s@ might throw a precise exception (ultimately via raiseIO#), then we must not force 'y', which may fail to terminate or throw an imprecise exception, until we have performed @foo x s@. So we have to 'deferAfterPreciseException' (which 'lub's with 'exnDmdType' to model the exceptional control flow) when @foo x s@ may throw a precise exception. Motivated by T13380{d,e,f}. See Note [Which scrutinees may throw precise exceptions] in "GHC.Core.Opt.DmdAnal". We have to be careful not to discard dead-end Divergence from case alternatives, though (#18086): m = putStrLn "foo" >> error "bar" 'm' should still have 'exnDiv', which is why it is not sufficient to lub with 'nopDmdType' (which has 'topDiv') in 'deferAfterPreciseException'. Historical Note: This used to be called the "IO hack". But that term is rather a bad fit because 1. It's easily confused with the "State hack", which also affects IO. 2. Neither "IO" nor "hack" is a good description of what goes on here, which is deferring strictness results after possibly throwing a precise exception. The "hack" is probably not having to defer when we can prove that the expression may not throw a precise exception (increasing precision of the analysis), but that's just a favourable guess.
References 3
- Which scrutinees may throw precise exceptions GHC.Core.Opt.DmdAnal
- Default demand on free variables and arguments GHC.Types.Demand
- Precise vs imprecise exceptions GHC.Types.Demand
Referenced by 13
- GHC.Types.Demand call site ×3
- GHC.Core.Opt.DmdAnal call site ×2
- Exceptions: asynchronous, synchronous, and unchecked GHC.Builtin.PrimOps
- Which scrutinees may throw precise exceptions GHC.Core.Opt.DmdAnal
- Floating primops GHC.Core.Opt.FloatIn
- Dead ends GHC.Types.Demand
- Side-effects and strictness GHC.Types.Demand
- Exceptions and strictness GHC.Types.Demand
- Default demand on free variables and arguments GHC.Types.Demand
- deferAfterPreciseException GHC.Types.Demand