Make everything BoxedRep have enter code
The recent UnliftedDatatypes
extension has turned up an annoying wart in our runtime system: While every lifted heap object I know of has enter code, primitive unlifted heap objects such as MutVar#
do not. Here's an example:
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE UnliftedDatatypes #-}
{-# LANGUAGE MagicHash, UnboxedTuples #-}
import Unsafe.Coerce
import Data.Kind
import Data.IORef
import GHC.Exts
import GHC.IO
sek :: forall (a :: UnliftedType) b. a -> b -> b
sek = unsafeCoerce seq
{-# OPAQUE sek #-}
data T :: UnliftedType where
MkT :: Int -> T
main = do
let t = MkT 42
t `sek` return ()
IO $ \s -> case newMutVar# (42 :: Int) s of -- can't use IO for non-lifted return types, hence unpacked here
(# s', mv #) -> case mv `sek` () of
() -> (# s', () #)
While the sek
on t
works perfectly fine, the sek
on mv
will crash today:
test: internal error: MUT_VAR_DIRTY object (0x4200404240) entered!
Stack trace:
test: Failed to get stack frames of current process: no matching address range: Invalid argument
0x7a7f42 set_initial_registers (rts/Libdw.c:294.5)
0x7fc63df84f98 dwfl_thread_getframes (/nix/store/7rnw4vh83qd4nza3n7ffsicwg5rbkz0m-elfutils-0.186/lib/libdw-0.186.so)
0x7fc63df84adb get_one_thread_cb (/nix/store/7rnw4vh83qd4nza3n7ffsicwg5rbkz0m-elfutils-0.186/lib/libdw-0.186.so)
0x7fc63df84e02 dwfl_getthreads (/nix/store/7rnw4vh83qd4nza3n7ffsicwg5rbkz0m-elfutils-0.186/lib/libdw-0.186.so)
0x7fc63df85347 dwfl_getthread_frames (/nix/store/7rnw4vh83qd4nza3n7ffsicwg5rbkz0m-elfutils-0.186/lib/libdw-0.186.so)
0x7a85d3 libdwGetBacktrace (rts/Libdw.c:265.5)
0x77a00d rtsFatalInternalErrorFn (rts/RtsMessages.c:176.22)
0x77a1ca barf (rts/RtsMessages.c:49.4)
0x79d5f5 stg_MUT_VAR_DIRTY_info (/tmp/test)
(GHC version 9.5.20220606 for x86_64_unknown_linux)
Please report this as a GHC bug: https://www.haskell.org/ghc/reportabug
[3] 594217 abort (core dumped) ./test
You might be saying "but you used unsafeCoerce
here, no wonder it falls apart!" and you are right. But I'd like to see #15532 fixed one day. That would allow the definition of sek
without any unsafeCoerce
. So after #15532, the observed crash would have been an actual bug.
Proposal
So I propose to add trivial enter code for the primitive MutVar#
, Array#
, etc.
Costs
You might think that code size would increase quite a bit. But
- There often are many more function definitions than primitive data types
- All primitive types can share their one implementation of enter code! Just tag the pointer and return.
Additionally, the panicking enter code is a useful sanity check, according to @bgamari and @AndreasK. We'd lose that if we replace it with something non-panicky.
Benefits
In return we get
- One step closer to #15532. Also it would be safe to use e.g.,
Lazy (MutVar# a)
in #21693. - The ability to make
Array#
,MutVar#
, etc. levity polymorphic. We could return them from actual thunks while saving the additional indirection that isArray
,IORef
, etc. Those can just becomenewtypes
over the Lifted version ofArray#
,MutVar#
. - Less confusion in #20849 (comment 439624)
- Less special cases in general. Primitive unlifted types become just like agebraic types where we don't know the RHS (same situation can arise for ADT declarations in .hs-boot files).