Open Profpatsch opened 2 years ago
cc @sheaf who wrote project
a while ago
I don't recall the exact details, but I think I saw an issue like this start happening with GHC 9.0 or 9.2. The issue went away with -fno-full-laziness
. What version of GHC are you using, and does the problem still happen with -fno-full-laziness
?
Yes, that fixes it … but why, that’s horrifying
How can every entry in a list of records pulled out of IO
be changed by laziness floating.
The relevant GHC issue is #19413 (although that is for runRW#
not runST
). See in particular my comment. This started happening with GHC 9.0.
I’m trying to reproduce the problem in the test suite, but I don’t know how to trigger the mis-optimization
https://github.com/agrafix/superrecord/pull/39/commits/085540be48ee8ef768878f9ce5f99307cfa087ef
Also cabal test
flat-out refuses to use the -O2
from the cabal file, you have to run cabal test -O2
@sheaf Do you by any chance have any time next week to create a minimal reproduction example for the GHC bug tracker?
@sheaf I meant doing a video call to figure one out based on the unsafePerformIO
example @ndmitchell gives in https://gitlab.haskell.org/ghc/ghc/-/issues/19413
@Profpatsch Could you try replacing, purely in this library,
runST' :: (forall s. ST s a) -> a
runST' !s = runST s
with
import GHC.ST (ST(..))
import GHC.Exts (runRW#, lazy)
runST' :: (forall s. ST s a) -> a
runST' !(ST st) = case runRW# st of (# _, a #) -> lazy a
If that works, I think that would be good evidence we need the same lazy
trick for runST
.
project
(and potentially alsoinject
, we haven’t verified`, will cause undefined behavior, which we noticed & verified in a production system.The bug only appears with optimizations enabled (only tested
-O2
), not in ghciI don’t have a minimal repro (yet), but I can describe what we did:
There was one function which loaded some data in
IO
, constructed a ist ofrec
, then before returning usedproject
to reduce unnecessary fields in every list element:And another function that pulled a subset of fields from the record, like this:
Let’s say the final data was
Then what would be actually returned was (!!):
now, if you rewrote the function to deeply evaluate the
project
ed record:It would again return
When we rewrote the original function to construct a new record instead of
project
ing:The problem went away.