GHC issueshttps://gitlab.haskell.org/ghc/ghc/-/issues2022-11-29T15:37:58Zhttps://gitlab.haskell.org/ghc/ghc/-/issues/22509Worse performance with -O2022-11-29T15:37:58ZmeooowWorse performance with -OSee master ticket #1168
## Summary
The following program reads a sequence of integers, puts them in an array, and prints them by index.
```hs
import Control.Monad
import Data.Array.Unboxed
main :: IO ()
main = do
n <- readLn
...See master ticket #1168
## Summary
The following program reads a sequence of integers, puts them in an array, and prints them by index.
```hs
import Control.Monad
import Data.Array.Unboxed
main :: IO ()
main = do
n <- readLn
xs <- map read . words <$> getLine
let a = listArray (1, n) xs :: UArray Int Int
q <- readLn
replicateM_ q $ do
i <- readLn
print (a!i)
```
Surprisingly, this runs a lot worse with `-O` than without.
From the core it appears that with `-O` the array is built on every step inside the `replicateM_`. This turns a simple O(n+q) program into O(nq).
## Steps to reproduce
Compile and run the above program with `-O`.
## Expected behavior
The array is constructed once.
## Environment
* GHC version used: 9.2.5
Optional:
* Operating System: Ubuntu
* System Architecture: x86_64https://gitlab.haskell.org/ghc/ghc/-/issues/21820`for_ [0..n]` is not unfolded for some monads2022-07-12T14:18:58ZJonathan Coates`for_ [0..n]` is not unfolded for some monads## Summary
It is relatively common to express an imperative `for`-style loop using `for_ [0..n] $ \i -> `. In many cases, GHC is able to inline the call to `enumFromTo` and entirely eliminate the list constructor.
However, there are so...## Summary
It is relatively common to express an imperative `for`-style loop using `for_ [0..n] $ \i -> `. In many cases, GHC is able to inline the call to `enumFromTo` and entirely eliminate the list constructor.
However, there are some monads where this does not occur, including some conceptually simple ones, making performance hard to reason about.
I realise this is probably a more general issue, so apologies if this has been reported before. I was not able to find something which looked similar in the issue tracker, but wasn't quite sure what to look for either!
## Steps to reproduce
Consider the following example:
```haskell
{-# OPTIONS_GHC -O2 -ddump-simpl -dsuppress-all #-}
module T (go) where
import Control.Monad.State.Strict
import Data.Foldable
import GHC.Exts
type OurMonad = State ()
go :: Int -> OurMonad ()
go n = do
x <- opaque ()
for_ [0..n] $ \i -> do
opaque x
opaque :: a -> OurMonad ()
opaque = noinline (\_ -> pure ())
-- The NOINLINE pragma isn't sufficient here, we need noinline.
```
Inspecting the generated core reveals the list construction is not eliminated:
<details><summary>Generated Core</summary>
```haskell
-- RHS size: {terms: 6, types: 5, coercions: 0, joins: 0/0}
go1 = \ @a_a1Cq _ s1_a1MQ -> ((), s1_a1MQ)
-- RHS size: {terms: 3, types: 5, coercions: 17, joins: 0/0}
opaque = \ @a_a1Cq -> noinline (go1 `cast` <Co:17>)
-- RHS size: {terms: 2, types: 1, coercions: 0, joins: 0/0}
go_m1 = opaque ()
-- RHS size: {terms: 58, types: 45, coercions: 26, joins: 1/4}
go
= \ n_a1tg ->
let {
lvl_s22B
= case n_a1tg of { I# y_a21W ->
case ># 0# y_a21W of {
__DEFAULT ->
letrec {
go3_a257
= \ x_a258 ->
: (I# x_a258)
(case ==# x_a258 y_a21W of {
__DEFAULT -> go3_a257 (+# x_a258 1#);
1# -> []
}); } in
go3_a257 0#;
1# -> []
}
} } in
(\ s1_a1N4 ->
case ((go_m1 `cast` <Co:4>) s1_a1N4) `cast` <Co:4> of
{ (a1_a1N7, s'_a1N8) ->
let { lvl1_s22A = opaque a1_a1N7 } in
joinrec {
go2_s25c ds_a235 eta_B0
= case ds_a235 of {
[] -> ((), eta_B0) `cast` <Co:5>;
: y_a238 ys_a239 ->
case ((lvl1_s22A `cast` <Co:4>) eta_B0) `cast` <Co:4> of
{ (a2_a22S, s'1_a22T) ->
jump go2_s25c ys_a239 s'1_a22T
}
}; } in
jump go2_s25c lvl_s22B s'_a1N8
})
`cast` <Co:5>
```
</details>
This was originally encountered in [some code which uses the `STT` and binary's `Get` monad](https://github.com/agda/agda/pull/5980).
## Expected behaviour
One would expect the list construction to be eliminated, and the comparison inlined into the join point's body. We can see this occur with other monads, such as `IO` or `ST`.
<details><summary>Generated Core with <code>type OurMonad = IO</code></summary>
```
go2 = \ @a_a1z3 _ s_a1IO -> (# s_a1IO, () #)
-- RHS size: {terms: 3, types: 5, coercions: 6, joins: 0/0}
opaque = \ @a_a1z3 -> noinline (go2 `cast` <Co:6>)
-- RHS size: {terms: 46, types: 61, coercions: 4, joins: 1/2}
go1
= \ n_a1sb s_a1J0 ->
case ((opaque ()) `cast` <Co:2>) s_a1J0 of
{ (# ipv_a1J2, ipv1_a1J3 #) ->
case n_a1sb of { I# y_a1XJ ->
case ># 0# y_a1XJ of {
__DEFAULT ->
let { lvl_s1Yk = opaque ipv1_a1J3 } in
joinrec {
go3_s20G x_a1XX s1_a1Yu
= case (lvl_s1Yk `cast` <Co:2>) s1_a1Yu of
{ (# ipv2_a1Yw, ipv3_a1Yx #) ->
case ==# x_a1XX y_a1XJ of {
__DEFAULT -> jump go3_s20G (+# x_a1XX 1#) ipv2_a1Yw;
1# -> (# ipv2_a1Yw, () #)
}
}; } in
jump go3_s20G 0# ipv_a1J2;
1# -> (# ipv_a1J2, () #)
}
}
}
```
</details>
I find this a little surprising, as `ST`/`IO` and `StateT` _look_ similar - I'm assuming there's special handling of the `State# s` token?
## Environment
* GHC version used: 9.2.3 and 9.4.0.20220501.
Optional:
* Operating System: Linux
* System Architecture: x86_64https://gitlab.haskell.org/ghc/ghc/-/issues/18435full-laziness optimization makes program run out of memory2021-02-16T21:31:07Zplsnpfull-laziness optimization makes program run out of memoryThe full laziness optimization pass seems to produce an executable that runs Out Of Memory. Without this optimization the executable runs without the OOM problem.
https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-opt...The full laziness optimization pass seems to produce an executable that runs Out Of Memory. Without this optimization the executable runs without the OOM problem.
https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html#ghc-flag--ffull-laziness
Please see the attachment for a full test case [tc1.zip](/uploads/124c4d599050eb458090400a176d14f9/tc1.zip). Go into package.yaml and comment / uncomment the -fno-full-laziness flag.
Command used to build and run the program: `reset && stack clean && stack build && rm -rf data_bench && echo "Running .." && /usr/bin/time stack exec -- tc1`
Setting -O0 and -ffull-laziness does NOT trigger the problem. -O1 must be set for the problem to become visible. As much as possible other optimization flags have been disabled to minimize the code transformation in the core-to-core pipeline (handy for when comparing with VS without full laziness).
Changing `GenerateCommand 2 1 11046 1 187960` to `GenerateCommand 1 1 11046 1 187960` in the source code also gets rid of the problem. This information might help to pinpoint the problem.
LTS 16.4 for ghc-8.8.3 was used as package set and compiler. 64 bits ubuntu 20.04
One of the following conclusions may be derived:
* My code was poorly written thus triggering the problem with full laziness optimization.
* This is normal that it happens sometimes, as stated in the documentation `Full laziness increases sharing, which can lead to increased memory residency.`
* Even if the code is poor and the optimization doesn't always work that well ("increased memory residency"), the OOM problem is still severe and normally should not happen, thus might be an indication of a bug in the full laziness optimization pass.https://gitlab.haskell.org/ghc/ghc/-/issues/18238Explore alternatives to the state hack2022-11-24T23:09:44ZSebastian GrafExplore alternatives to the state hackThe state hack (`-fstate-hack`, on by default, see `Note [The state-transformer hack]` in G.C.O.Arity) is a smart hack without which efficient compilation of `IO` wouldn't be possible. But fixes such as https://gitlab.haskell.org/ghc/ghc...The state hack (`-fstate-hack`, on by default, see `Note [The state-transformer hack]` in G.C.O.Arity) is a smart hack without which efficient compilation of `IO` wouldn't be possible. But fixes such as https://gitlab.haskell.org/ghc/ghc/-/commit/69c0e16e9ce9fe045851c09425956fa3407824ae?merge_request_iid=3318 made me thinking: Now that we have `GHC.Magic.oneShot`, can we get by without?
Suppose we define (in `ghc-prim:GHC.Types`):
```hs
newtype IO a = IONoEta (State# RealWorld -> (# State# RealWorld, a #))
pattern IO m <- IONoEta m
where
IO m = IONoEta (oneShot m)
```
Similarly for `ST`. I hope that we can get rid of the state hack this way. That would also fix #14596.
Another advantage to this is that we can use `IONoEta` on a use by use basis, rather than having the rather unobtrusive `{-# OPTIONS_GHC -fno-state-hack #-}` at the top of the module, disconnected from its usage.https://gitlab.haskell.org/ghc/ghc/-/issues/18202Check eta-expansion in the compiler2021-04-06T14:19:02ZSylvain HenryCheck eta-expansion in the compiler@nomeata reported on his blog [1] that the ReaderT pattern (i.e. a newtype containing a function) can lead to missed eta-expansions. In his case he reports "Improvement: Allocations: -23.20% Time: -23.00%"
We use this pattern in GHC too...@nomeata reported on his blog [1] that the ReaderT pattern (i.e. a newtype containing a function) can lead to missed eta-expansions. In his case he reports "Improvement: Allocations: -23.20% Time: -23.00%"
We use this pattern in GHC too so we should check if we could gain anything by forcing eta-expansions in a few places.
MR !3309
Things done:
* !3503: make the unifier use a one-shot monad.
* !3751: make the Simplifier use a one-shot monad
[1] https://www.joachim-breitner.de/blog/763-Faster_Winter_5__Eta-Expanding_ReaderT
---------
Edit by me (@AndreasK): We should just check all Monads. So far it seems this was beneficial to any monad it was applied to.
Based on grep here is a list of Monads in GHC:
Monads and checked:
- [ ] compiler/GHC/ByteCode/Asm.hs:instance Monad Assembler where
- [ ] compiler/GHC/Cmm/Lint.hs:instance Monad CmmLint where
- [ ] compiler/GHC/Cmm/Parser/Monad.hs:instance Monad PD where
- [ ] compiler/GHC/CmmToAsm/CFG/Dominators.hs:instance Monad (S z s) where
- [ ] compiler/GHC/CmmToAsm/Monad.hs:instance Monad NatM where
- [x] compiler/GHC/CmmToAsm/Reg/Linear/State.hs:instance Monad (RegM freeRegs) where
!4759
- [ ] compiler/GHC/CmmToAsm.hs:instance Monad CmmOptM where
- [ ] compiler/GHC/CmmToC.hs:instance Monad TE where
- [ ] compiler/GHC/CmmToLlvm/Base.hs:instance Monad LlvmM where
- [ ] compiler/GHC/Core/FamInstEnv.hs:instance Monad NormM where
- [ ] compiler/GHC/Core/Lint.hs:instance Monad LintM where
- [ ] compiler/GHC/Core/Opt/ConstantFold.hs:instance Monad RuleM where
- [ ] compiler/GHC/Core/Opt/Monad.hs:instance Monad CoreM where
- [ ] compiler/GHC/Core/Opt/Monad.hs-boot:instance Monad CoreM
- [x] compiler/GHC/Core/Opt/Simplify/Monad.hs:instance Monad SimplM where
- [ ] compiler/GHC/Core/Unify.hs:instance Monad UnifyResultM where
- [ ] compiler/GHC/Core/Unify.hs:instance Monad UM where
- [ ] compiler/GHC/CoreToByteCode.hs:instance Monad BcM where
- [ ] compiler/GHC/CoreToStg.hs:instance Monad CtsM where
- [x] compiler/GHC/Data/IOEnv.hs:instance Monad (IOEnv m) where
- [ ] compiler/GHC/Data/Maybe.hs:instance Monad (MaybeErr err) where
- [ ] compiler/GHC/Data/Stream.hs:instance Monad f => Functor (Stream f a) where
- [ ] compiler/GHC/Data/Stream.hs:instance Monad m => Applicative (Stream m a) where
- [ ] compiler/GHC/Data/Stream.hs:instance Monad m => Monad (Stream m a) where
- [ ] compiler/GHC/Driver/CmdLine.hs:instance Monad m => Functor (EwM m) where
- [ ] compiler/GHC/Driver/CmdLine.hs:instance Monad m => Applicative (EwM m) where
- [ ] compiler/GHC/Driver/CmdLine.hs:instance Monad m => Monad (EwM m) where
- [ ] compiler/GHC/Driver/CmdLine.hs:instance Monad (CmdLineP s) where
- [ ] compiler/GHC/Driver/Env/Types.hs:instance Monad Hsc where
- [ ] compiler/GHC/Driver/Monad.hs:instance Monad Ghc where
- [ ] compiler/GHC/Driver/Monad.hs:instance Monad m => Monad (GhcT m) where
- [ ] compiler/GHC/Driver/Pipeline/Monad.hs:instance Monad CompPipeline where
- [ ] compiler/GHC/HsToCore/Coverage.hs:instance Monad TM where
- [ ] compiler/GHC/Iface/Tidy.hs:instance Monad DFFV where
- [ ] compiler/GHC/Parser/Lexer.x:instance Monad P where
- [ ] compiler/GHC/Parser/PostProcess.hs:instance Monad PV where
- [ ] compiler/GHC/Rename/Pat.hs:instance Monad CpsRn where
- [ ] compiler/GHC/Stg/Lint.hs:instance Monad LintM where
- [ ] compiler/GHC/StgToCmm/ExtCode.hs:instance Monad CmmParse where
- [ ] compiler/GHC/StgToCmm/Monad.hs:instance Monad FCode where
- [ ] compiler/GHC/Tc/Deriv.hs: instance Monad [] => Monad S -- by coercion sym (Monad :CoS) : Monad [] ~ Monad S
- [ ] compiler/GHC/Tc/Deriv.hs: instance Monad [] => Monad (T Int) -- only if we can eta reduce???
- [ ] compiler/GHC/Tc/Deriv.hs: instance Monad [] => Monad (T Int) -- only if we can eta reduce???
- [ ] compiler/GHC/Tc/Deriv.hs: -- instance Monad (ST s) => Monad (T s) where
- [x] compiler/GHC/Tc/Solver/Monad.hs:instance Monad TcS where
- [x] compiler/GHC/Tc/Solver/Rewrite.hs:instance Monad RewriteM where
- [ ] compiler/GHC/Tc/TyCl/Utils.hs:instance Monad SynCycleM where
- [ ] compiler/GHC/Tc/TyCl/Utils.hs:instance Monad RoleM where
- [ ] compiler/GHC/Tc/Types.hs:instance Monad TcPluginM where
- [ ] compiler/GHC/ThToHs.hs:instance Monad CvtM where
- [ ] compiler/GHC/Types/Unique/Supply.hs:instance Monad UniqSM where
- [ ] compiler/GHC/Utils/Monad/State.hs:instance Monad (State s) where
- [ ] compiler/GHC/Utils/Monad.hs: instance Monad M wherehttps://gitlab.haskell.org/ghc/ghc/-/issues/14596Remove uses of unsafeGlobalDynFlags for state hack2020-06-02T19:51:39ZBen GamariRemove uses of unsafeGlobalDynFlags for state hackCurrently there are a variety of uses of the terrible `unsafeGlobalDynFlags` scattered about the compiler to implement `-fno-state-hack`. This global state makes parallel compilation unreliable and complicates API usage. Remove these use...Currently there are a variety of uses of the terrible `unsafeGlobalDynFlags` scattered about the compiler to implement `-fno-state-hack`. This global state makes parallel compilation unreliable and complicates API usage. Remove these uses.
<details><summary>Trac metadata</summary>
| Trac field | Value |
| ---------------------- | ------------ |
| Version | 8.2.1 |
| Type | Task |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Compiler |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | |
| Operating system | |
| Architecture | |
</details>
<!-- {"blocked_by":[],"summary":"Remove uses of unsafeGlobalDynFlags for state hack","status":"New","operating_system":"","component":"Compiler","related":[],"milestone":"","resolution":"Unresolved","owner":{"tag":"Unowned"},"version":"8.2.1","keywords":[],"differentials":[],"test_case":"","architecture":"","cc":[""],"type":"Task","description":"Currently there are a variety of uses of the terrible `unsafeGlobalDynFlags` scattered about the compiler to implement `-fno-state-hack`. This global state makes parallel compilation unreliable and complicates API usage. Remove these uses.","type_of_failure":"OtherFailure","blocking":[]} -->https://gitlab.haskell.org/ghc/ghc/-/issues/9388Narrow the scope of the notorious "state hack"2022-12-13T14:10:50ZSimon Peyton JonesNarrow the scope of the notorious "state hack"The "state hack" has caused any number of bug reports (just search for that string), the most recent of which is #9349.
Here's an idea to make it less pervasive: (roughly) use the state hack only for top-level functions definitions.
Th...The "state hack" has caused any number of bug reports (just search for that string), the most recent of which is #9349.
Here's an idea to make it less pervasive: (roughly) use the state hack only for top-level functions definitions.
The idea is that for nested lambdas the context should give the one-shot-ness, now that we are equipped with cardinality analysis. For example, consider the call
```
replicatM_ 1000 (\(s :: RealWorld#) -> blah)
```
The lambda is 1000-shot, not one-shot, notwithstanding the type of the binder. Moreover `replicateM_`'s strictness/cardinality signature will say just that, and GHC already knows how to propagate that information onto the `\s`.
But for top level functions like
```
pr :: String -> IO ()
pr x = putStrLn (reverse x)
```
we get Core
```
pr = \x. let y = reverse x in
\ (s :: State# RealWorld). putStrLn y s
```
and, since we can't see all the callers of `pr`, we don't know if work is lost by pushing the `reverse` call inside, to get
```
pr = \x. (s :: State# RealWorld). putStrLn (reverse x) s
```
which is much more efficient. Indeed, this might not be so good if the calls looked like
```
... replicateM_ 1000 (pr "foo")...
```
because then "foo" will be reversed 1000 times. But arguably that's what the programmer expects anyway, looking at the code; and the efficiency hit from not eta-expanding all functions like `pr` (which are very very common) is significant.
The point is the that the only ones that need hacking are the top-level guys, and maybe even the top-level *exported* guys.
I have not fully thought this through, let alone tried it out, but I wanted to capture the thought. It would need some careful performance testing.
Simon
<details><summary>Trac metadata</summary>
| Trac field | Value |
| ---------------------- | ------------ |
| Version | 7.8.2 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Compiler |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | |
| Operating system | |
| Architecture | |
</details>
<!-- {"blocked_by":[],"summary":"Narrow the scope of the notorious \"state hack\"","status":"New","operating_system":"","component":"Compiler","related":[],"milestone":"","resolution":"Unresolved","owner":{"tag":"Unowned"},"version":"7.8.2","keywords":[],"differentials":[],"test_case":"","architecture":"","cc":[""],"type":"Bug","description":"The \"state hack\" has caused any number of bug reports (just search for that string), the most recent of which is #9349.\r\n\r\nHere's an idea to make it less pervasive: (roughly) use the state hack only for top-level functions definitions.\r\n\r\nThe idea is that for nested lambdas the context should give the one-shot-ness, now that we are equipped with cardinality analysis. For example, consider the call\r\n{{{\r\n replicatM_ 1000 (\\(s :: RealWorld#) -> blah)\r\n}}}\r\nThe lambda is 1000-shot, not one-shot, notwithstanding the type of the binder. Moreover `replicateM_`'s strictness/cardinality signature will say just that, and GHC already knows how to propagate that information onto the `\\s`.\r\n\r\nBut for top level functions like\r\n{{{\r\npr :: String -> IO ()\r\npr x = putStrLn (reverse x)\r\n}}}\r\nwe get Core\r\n{{{\r\npr = \\x. let y = reverse x in\r\n \\ (s :: State# RealWorld). putStrLn y s\r\n}}}\r\nand, since we can't see all the callers of `pr`, we don't know if work is lost by pushing the `reverse` call inside, to get\r\n{{{\r\npr = \\x. (s :: State# RealWorld). putStrLn (reverse x) s\r\n}}}\r\nwhich is much more efficient. Indeed, this might not be so good if the calls looked like\r\n{{{\r\n ... replicateM_ 1000 (pr \"foo\")...\r\n}}}\r\nbecause then \"foo\" will be reversed 1000 times. But arguably that's what the programmer expects anyway, looking at the code; and the efficiency hit from not eta-expanding all functions like `pr` (which are very very common) is significant.\r\n\r\nThe point is the that the only ones that need hacking are the top-level guys, and maybe even the top-level ''exported'' guys.\r\n\r\nI have not fully thought this through, let alone tried it out, but I wanted to capture the thought. It would need some careful performance testing.\r\n\r\nSimon","type_of_failure":"OtherFailure","blocking":[]} -->https://gitlab.haskell.org/ghc/ghc/-/issues/9349excessive inlining due to state hack2020-06-02T23:44:20Zrwbartonexcessive inlining due to state hackThe program at https://gist.github.com/jorendorff/a3005968adc8f054baf7 runs very slowly when compiled with `-O` or higher. It seems that `arr` and/or `rangeMap` is being inlined into the do block on lines 89-91 and recomputed for each it...The program at https://gist.github.com/jorendorff/a3005968adc8f054baf7 runs very slowly when compiled with `-O` or higher. It seems that `arr` and/or `rangeMap` is being inlined into the do block on lines 89-91 and recomputed for each iteration of the loop. The program runs nearly instantly when compiled with `-O0` or with `-O -fno-pre-inlining`. (Of course, this does not mean `-fpre-inlining` is necessary the culprit; it could be enabling some subsequent misoptimization.)
<details><summary>Trac metadata</summary>
| Trac field | Value |
| ---------------------- | ------------ |
| Version | 7.8.3 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Compiler |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | |
| Operating system | |
| Architecture | |
</details>
<!-- {"blocked_by":[],"summary":"excessive inlining with -fpre-inlining","status":"New","operating_system":"","component":"Compiler","related":[],"milestone":"","resolution":"Unresolved","owner":{"tag":"Unowned"},"version":"7.8.3","keywords":[],"differentials":[],"test_case":"","architecture":"","cc":[""],"type":"Bug","description":"The program at https://gist.github.com/jorendorff/a3005968adc8f054baf7 runs very slowly when compiled with `-O` or higher. It seems that `arr` and/or `rangeMap` is being inlined into the do block on lines 89-91 and recomputed for each iteration of the loop. The program runs nearly instantly when compiled with `-O0` or with `-O -fno-pre-inlining`. (Of course, this does not mean `-fpre-inlining` is necessary the culprit; it could be enabling some subsequent misoptimization.)","type_of_failure":"OtherFailure","blocking":[]} -->https://gitlab.haskell.org/ghc/ghc/-/issues/7411Exceptions are optimized away in certain situations2020-05-27T16:04:30ZSimon Hengelsol@typeful.netExceptions are optimized away in certain situationsThe issue came up in [this thread on glasgow-haskell-users](http://www.haskell.org/pipermail/glasgow-haskell-users/2012-November/023027.html).
## Steps to reproduce:
```hs
-- file Foo.hs
import Control.Exception
import Control.DeepSeq
...The issue came up in [this thread on glasgow-haskell-users](http://www.haskell.org/pipermail/glasgow-haskell-users/2012-November/023027.html).
## Steps to reproduce:
```hs
-- file Foo.hs
import Control.Exception
import Control.DeepSeq
main = evaluate (('a' : undefined) `deepseq` return () :: IO ())
```
```
$ ghc -fforce-recomp -fpedantic-bottoms -O Foo.hs
```
### Expected result:
The program should fail with:
```
Foo: Prelude.undefined
```
### Actual result:
The program succeeds.
Compiling the program with `-fno-state-hack` helps.8.10.2Tobias Dammerstdammers@gmail.comTobias Dammerstdammers@gmail.comhttps://gitlab.haskell.org/ghc/ghc/-/issues/1168The dreaded State Hack. Optimisation can lose sharing in IO code, esp repli...2023-01-04T11:11:11ZSimon MarlowThe dreaded State Hack. Optimisation can lose sharing in IO code, esp replicateM## Summary
Many bug reports are ultimately due to the "state hack". This ticket serves a summary of these various reports, and points to thinking about solutions.
Other tickets reporting the same problem (please extend this list; "clo...## Summary
Many bug reports are ultimately due to the "state hack". This ticket serves a summary of these various reports, and points to thinking about solutions.
Other tickets reporting the same problem (please extend this list; "closed" probably means "duplicate" not "solved):
* #2284
* #1957
* #3629
* #7561
* #9349
* #10102
* #10401
* #10825
* #11271
* #11365
* #11677
* #11795
* #13406
* #21785
* #22509
Possible solutions
* #9388
* #18238
## This particular ticket's report
The simplifier is losing sharing in spectral/calendar, probably because we're being a bit fast-and-loose with eta-expanding State\# lambdas. A quick look at the Core shows that `calFor year` is not shared across multiple executions.
<details><summary>Trac metadata</summary>
| Trac field | Value |
| ---------------------- | ------------ |
| Version | 6.6 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Compiler |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | |
| Operating system | Unknown |
| Architecture | Unknown |
</details>
<!-- {"blocked_by":[],"summary":"nofib/spectral/calendar is mis-optimised","status":"New","operating_system":"Unknown","component":"Compiler","related":[],"milestone":"6.6.2","resolution":"Unresolved","owner":{"tag":"Unowned"},"version":"6.6","keywords":[],"differentials":[],"test_case":"","architecture":"Unknown","cc":[""],"type":"Bug","description":"The simplifier is losing sharing in spectral/calendar, probably because we're being a bit fast-and-loose with eta-expanding State# lambdas. A quick look at the Core shows that `calFor year` is not shared across multiple executions.","type_of_failure":"OtherFailure","blocking":[]} -->